Merge branch 'version-13-beta-pre-release' into version-13-beta
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 18294f3..9f65825 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '13.0.0-beta.3'
+__version__ = '13.0.0-beta.4'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/accounts_dashboard/accounts/accounts.json b/erpnext/accounts/accounts_dashboard/accounts/accounts.json
new file mode 100644
index 0000000..2fab50e
--- /dev/null
+++ b/erpnext/accounts/accounts_dashboard/accounts/accounts.json
@@ -0,0 +1,58 @@
+{
+ "cards": [
+ {
+ "card": "Total Outgoing Bills"
+ },
+ {
+ "card": "Total Incoming Bills"
+ },
+ {
+ "card": "Total Incoming Payment"
+ },
+ {
+ "card": "Total Outgoing Payment"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "Profit and Loss",
+ "width": "Full"
+ },
+ {
+ "chart": "Incoming Bills (Purchase Invoice)",
+ "width": "Half"
+ },
+ {
+ "chart": "Outgoing Bills (Sales Invoice)",
+ "width": "Half"
+ },
+ {
+ "chart": "Accounts Receivable Ageing",
+ "width": "Half"
+ },
+ {
+ "chart": "Accounts Payable Ageing",
+ "width": "Half"
+ },
+ {
+ "chart": "Budget Variance",
+ "width": "Full"
+ },
+ {
+ "chart": "Bank Balance",
+ "width": "Full"
+ }
+ ],
+ "creation": "2020-07-17 11:25:34.796608",
+ "dashboard_name": "Accounts",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 0,
+ "is_standard": 1,
+ "modified": "2020-07-22 13:07:34.540574",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Accounts",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/dashboard_chart/accounts_payable_ageing/accounts_payable_ageing.json b/erpnext/accounts/dashboard_chart/accounts_payable_ageing/accounts_payable_ageing.json
new file mode 100644
index 0000000..fb5ee64
--- /dev/null
+++ b/erpnext/accounts/dashboard_chart/accounts_payable_ageing/accounts_payable_ageing.json
@@ -0,0 +1,23 @@
+{
+ "chart_name": "Accounts Payable Ageing",
+ "chart_type": "Report",
+ "creation": "2020-07-17 11:25:34.564015",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"report_date\":\"frappe.datetime.now_date()\"}",
+ "filters_json": "{\"ageing_based_on\":\"Due Date\",\"range1\":30,\"range2\":60,\"range3\":90,\"range4\":120,\"group_by_party\":0,\"based_on_payment_terms\":0}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 12:29:33.584419",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Accounts Payable Ageing",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Accounts Payable",
+ "timeseries": 0,
+ "type": "Donut",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/dashboard_chart/accounts_receivable_ageing/accounts_receivable_ageing.json b/erpnext/accounts/dashboard_chart/accounts_receivable_ageing/accounts_receivable_ageing.json
new file mode 100644
index 0000000..48ec781
--- /dev/null
+++ b/erpnext/accounts/dashboard_chart/accounts_receivable_ageing/accounts_receivable_ageing.json
@@ -0,0 +1,23 @@
+{
+ "chart_name": "Accounts Receivable Ageing",
+ "chart_type": "Report",
+ "creation": "2020-07-17 11:25:34.535388",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"report_date\":\"frappe.datetime.now_date()\"}",
+ "filters_json": "{\"ageing_based_on\":\"Due Date\",\"range1\":30,\"range2\":60,\"range3\":90,\"range4\":120,\"group_by_party\":0,\"based_on_payment_terms\":0,\"show_future_payments\":0,\"show_delivery_notes\":0,\"show_sales_person\":0}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 12:28:42.743551",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Accounts Receivable Ageing",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Accounts Receivable",
+ "timeseries": 0,
+ "type": "Donut",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/dashboard_chart/bank_balance/bank_balance.json b/erpnext/accounts/dashboard_chart/bank_balance/bank_balance.json
new file mode 100644
index 0000000..6442c02
--- /dev/null
+++ b/erpnext/accounts/dashboard_chart/bank_balance/bank_balance.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "Bank Balance",
+ "chart_type": "Custom",
+ "creation": "2020-07-17 11:25:34.620221",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"account\":\"locals[\\\":Company\\\"][frappe.defaults.get_user_default(\\\"Company\\\")][\\\"default_bank_account\\\"]\"}",
+ "filters_json": "{}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 12:19:59.879476",
+ "modified": "2020-07-22 12:21:48.780513",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Bank Balance",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "source": "Account Balance Timeline",
+ "time_interval": "Quarterly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Line",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json
new file mode 100644
index 0000000..8631d3d
--- /dev/null
+++ b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json
@@ -0,0 +1,23 @@
+{
+ "chart_name": "Budget Variance",
+ "chart_type": "Report",
+ "creation": "2020-07-17 11:25:34.593061",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
+ "filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 12:24:49.144210",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Budget Variance",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Budget Variance Report",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git "a/erpnext/accounts/dashboard_chart/incoming_bills_\050purchase_invoice\051/incoming_bills_\050purchase_invoice\051.json" "b/erpnext/accounts/dashboard_chart/incoming_bills_\050purchase_invoice\051/incoming_bills_\050purchase_invoice\051.json"
new file mode 100644
index 0000000..55f0d77
--- /dev/null
+++ "b/erpnext/accounts/dashboard_chart/incoming_bills_\050purchase_invoice\051/incoming_bills_\050purchase_invoice\051.json"
@@ -0,0 +1,29 @@
+{
+ "based_on": "posting_date",
+ "chart_name": "Incoming Bills (Purchase Invoice)",
+ "chart_type": "Sum",
+ "color": "#a83333",
+ "creation": "2020-07-17 11:25:34.479703",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Purchase Invoice",
+ "dynamic_filters_json": "",
+ "filters_json": "[[\"Purchase Invoice\",\"docstatus\",\"=\",1]]",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-21 17:37:30.727306",
+ "modified": "2020-07-21 17:51:07.374917",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Incoming Bills (Purchase Invoice)",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 1,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "value_based_on": "base_net_total",
+ "y_axis": []
+}
\ No newline at end of file
diff --git "a/erpnext/accounts/dashboard_chart/outgoing_bills_\050sales_invoice\051/outgoing_bills_\050sales_invoice\051.json" "b/erpnext/accounts/dashboard_chart/outgoing_bills_\050sales_invoice\051/outgoing_bills_\050sales_invoice\051.json"
new file mode 100644
index 0000000..45de667
--- /dev/null
+++ "b/erpnext/accounts/dashboard_chart/outgoing_bills_\050sales_invoice\051/outgoing_bills_\050sales_invoice\051.json"
@@ -0,0 +1,28 @@
+{
+ "based_on": "posting_date",
+ "chart_name": "Outgoing Bills (Sales Invoice)",
+ "chart_type": "Sum",
+ "color": "#7b933d",
+ "creation": "2020-07-17 11:25:34.507547",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Sales Invoice",
+ "filters_json": "[[\"Sales Invoice\",\"docstatus\",\"=\",1]]",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-21 17:37:31.574666",
+ "modified": "2020-07-21 17:52:03.970530",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Outgoing Bills (Sales Invoice)",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 1,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "value_based_on": "base_net_total",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json b/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json
new file mode 100644
index 0000000..3fa995b
--- /dev/null
+++ b/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json
@@ -0,0 +1,23 @@
+{
+ "chart_name": "Profit and Loss",
+ "chart_type": "Report",
+ "creation": "2020-07-17 11:25:34.448572",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
+ "filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 12:33:48.888943",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Profit and Loss",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Profit and Loss Statement",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py
deleted file mode 100644
index b2abffc..0000000
--- a/erpnext/accounts/dashboard_fixtures.py
+++ /dev/null
@@ -1,284 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-import json
-from frappe.utils import nowdate, add_months, get_date_str
-from frappe import _
-from erpnext.accounts.utils import get_fiscal_year, get_account_name, FiscalYearError
-
-def _get_fiscal_year(date=None):
- try:
- fiscal_year = get_fiscal_year(date=nowdate(), as_dict=True)
- return fiscal_year
-
- except FiscalYearError:
- #if no fiscal year for current date then get default fiscal year
- try:
- fiscal_year = get_fiscal_year(as_dict=True)
- return fiscal_year
-
- except FiscalYearError:
- #if still no fiscal year found then no accounting data created, return
- return None
-
-def get_company_for_dashboards():
- company = frappe.defaults.get_defaults().company
- if company:
- return company
- else:
- company_list = frappe.get_list("Company")
- if company_list:
- return company_list[0].name
- return None
-
-def get_data():
-
- fiscal_year = _get_fiscal_year(nowdate())
-
- if not fiscal_year:
- return frappe._dict()
-
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(fiscal_year),
- "number_cards": get_number_cards(fiscal_year)
- })
-
-def get_dashboards():
- return [{
- "name": "Accounts",
- "dashboard_name": "Accounts",
- "doctype": "Dashboard",
- "charts": [
- { "chart": "Profit and Loss" , "width": "Full"},
- { "chart": "Incoming Bills (Purchase Invoice)", "width": "Half"},
- { "chart": "Outgoing Bills (Sales Invoice)", "width": "Half"},
- { "chart": "Accounts Receivable Ageing", "width": "Half"},
- { "chart": "Accounts Payable Ageing", "width": "Half"},
- { "chart": "Budget Variance", "width": "Full"},
- { "chart": "Bank Balance", "width": "Full"}
- ],
- "cards": [
- {"card": "Total Outgoing Bills"},
- {"card": "Total Incoming Bills"},
- {"card": "Total Incoming Payment"},
- {"card": "Total Outgoing Payment"}
- ]
- }]
-
-def get_charts(fiscal_year):
- company = frappe.get_doc("Company", get_company_for_dashboards())
- bank_account = company.default_bank_account or get_account_name("Bank", company=company.name)
- default_cost_center = company.cost_center
-
- return [
- {
- "doctype": "Dashboard Charts",
- "name": "Profit and Loss",
- "owner": "Administrator",
- "report_name": "Profit and Loss Statement",
- "filters_json": json.dumps({
- "company": company.name,
- "filter_based_on": "Fiscal Year",
- "from_fiscal_year": fiscal_year.get('name'),
- "to_fiscal_year": fiscal_year.get('name'),
- "periodicity": "Monthly",
- "include_default_book_entries": 1
- }),
- "type": "Bar",
- 'timeseries': 0,
- "chart_type": "Report",
- "chart_name": _("Profit and Loss"),
- "is_custom": 1,
- "is_public": 1
- },
- {
- "doctype": "Dashboard Chart",
- "time_interval": "Monthly",
- "name": "Incoming Bills (Purchase Invoice)",
- "chart_name": _("Incoming Bills (Purchase Invoice)"),
- "timespan": "Last Year",
- "color": "#a83333",
- "value_based_on": "base_net_total",
- "filters_json": json.dumps([["Purchase Invoice", "docstatus", "=", 1]]),
- "chart_type": "Sum",
- "timeseries": 1,
- "based_on": "posting_date",
- "owner": "Administrator",
- "document_type": "Purchase Invoice",
- "type": "Bar",
- "width": "Half",
- "is_public": 1
- },
- {
- "doctype": "Dashboard Chart",
- "name": "Outgoing Bills (Sales Invoice)",
- "time_interval": "Monthly",
- "chart_name": _("Outgoing Bills (Sales Invoice)"),
- "timespan": "Last Year",
- "color": "#7b933d",
- "value_based_on": "base_net_total",
- "filters_json": json.dumps([["Sales Invoice", "docstatus", "=", 1]]),
- "chart_type": "Sum",
- "timeseries": 1,
- "based_on": "posting_date",
- "owner": "Administrator",
- "document_type": "Sales Invoice",
- "type": "Bar",
- "width": "Half",
- "is_public": 1
- },
- {
- "doctype": "Dashboard Charts",
- "name": "Accounts Receivable Ageing",
- "owner": "Administrator",
- "report_name": "Accounts Receivable",
- "filters_json": json.dumps({
- "company": company.name,
- "report_date": nowdate(),
- "ageing_based_on": "Due Date",
- "range1": 30,
- "range2": 60,
- "range3": 90,
- "range4": 120
- }),
- "type": "Donut",
- 'timeseries': 0,
- "chart_type": "Report",
- "chart_name": _("Accounts Receivable Ageing"),
- "is_custom": 1,
- "is_public": 1
- },
- {
- "doctype": "Dashboard Charts",
- "name": "Accounts Payable Ageing",
- "owner": "Administrator",
- "report_name": "Accounts Payable",
- "filters_json": json.dumps({
- "company": company.name,
- "report_date": nowdate(),
- "ageing_based_on": "Due Date",
- "range1": 30,
- "range2": 60,
- "range3": 90,
- "range4": 120
- }),
- "type": "Donut",
- 'timeseries': 0,
- "chart_type": "Report",
- "chart_name": _("Accounts Payable Ageing"),
- "is_custom": 1,
- "is_public": 1
- },
- {
- "doctype": "Dashboard Charts",
- "name": "Budget Variance",
- "owner": "Administrator",
- "report_name": "Budget Variance Report",
- "filters_json": json.dumps({
- "company": company.name,
- "from_fiscal_year": fiscal_year.get('name'),
- "to_fiscal_year": fiscal_year.get('name'),
- "period": "Monthly",
- "budget_against": "Cost Center"
- }),
- "type": "Bar",
- "timeseries": 0,
- "chart_type": "Report",
- "chart_name": _("Budget Variance"),
- "is_custom": 1,
- "is_public": 1
- },
- {
- "doctype": "Dashboard Charts",
- "name": "Bank Balance",
- "time_interval": "Quarterly",
- "chart_name": "Bank Balance",
- "timespan": "Last Year",
- "filters_json": json.dumps({
- "company": company.name,
- "account": bank_account
- }),
- "source": "Account Balance Timeline",
- "chart_type": "Custom",
- "timeseries": 1,
- "owner": "Administrator",
- "type": "Line",
- "width": "Half",
- "is_public": 1
- },
- ]
-
-def get_number_cards(fiscal_year):
-
- year_start_date = get_date_str(fiscal_year.get("year_start_date"))
- year_end_date = get_date_str(fiscal_year.get("year_end_date"))
- return [
- {
- "doctype": "Number Card",
- "document_type": "Payment Entry",
- "name": "Total Incoming Payment",
- "filters_json": json.dumps([
- ['Payment Entry', 'docstatus', '=', 1],
- ['Payment Entry', 'posting_date', 'between', [year_start_date, year_end_date]],
- ['Payment Entry', 'payment_type', '=', 'Receive']
- ]),
- "label": _("Total Incoming Payment"),
- "function": "Sum",
- "aggregate_function_based_on": "base_received_amount",
- "is_public": 1,
- "is_custom": 1,
- "show_percentage_stats": 1,
- "stats_time_interval": "Monthly"
- },
- {
- "doctype": "Number Card",
- "document_type": "Payment Entry",
- "name": "Total Outgoing Payment",
- "filters_json": json.dumps([
- ['Payment Entry', 'docstatus', '=', 1],
- ['Payment Entry', 'posting_date', 'between', [year_start_date, year_end_date]],
- ['Payment Entry', 'payment_type', '=', 'Pay']
- ]),
- "label": _("Total Outgoing Payment"),
- "function": "Sum",
- "aggregate_function_based_on": "base_paid_amount",
- "is_public": 1,
- "is_custom": 1,
- "show_percentage_stats": 1,
- "stats_time_interval": "Monthly"
- },
- {
- "doctype": "Number Card",
- "document_type": "Sales Invoice",
- "name": "Total Outgoing Bills",
- "filters_json": json.dumps([
- ['Sales Invoice', 'docstatus', '=', 1],
- ['Sales Invoice', 'posting_date', 'between', [year_start_date, year_end_date]]
- ]),
- "label": _("Total Outgoing Bills"),
- "function": "Sum",
- "aggregate_function_based_on": "base_net_total",
- "is_public": 1,
- "is_custom": 1,
- "show_percentage_stats": 1,
- "stats_time_interval": "Monthly"
- },
- {
- "doctype": "Number Card",
- "document_type": "Purchase Invoice",
- "name": "Total Incoming Bills",
- "filters_json": json.dumps([
- ['Purchase Invoice', 'docstatus', '=', 1],
- ['Purchase Invoice', 'posting_date', 'between', [year_start_date, year_end_date]]
- ]),
- "label": _("Total Incoming Bills"),
- "function": "Sum",
- "aggregate_function_based_on": "base_net_total",
- "is_public": 1,
- "is_custom": 1,
- "show_percentage_stats": 1,
- "stats_time_interval": "Monthly"
- }
- ]
diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json
index 31315e4..a249783 100644
--- a/erpnext/accounts/desk_page/accounting/accounting.json
+++ b/erpnext/accounts/desk_page/accounting/accounting.json
@@ -148,9 +148,14 @@
"type": "Report"
},
{
+ "label": "Point of Sale",
+ "link_to": "point-of-sale",
+ "type": "Page"
+ },
+ {
"label": "Dashboard",
"link_to": "Accounts",
"type": "Dashboard"
}
]
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index c6de641..164f120 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -244,6 +244,8 @@
super(Account, self).on_trash(True)
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select name from tabAccount
where is_group = 1 and docstatus != 2 and company = %s
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index e4b96ae..b2e8b09 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -19,7 +19,6 @@
"unlink_payment_on_cancellation_of_invoice",
"unlink_advance_payment_on_cancelation_of_order",
"book_asset_depreciation_entry_automatically",
- "allow_cost_center_in_entry_of_bs_account",
"add_taxes_from_item_tax_template",
"automatically_fetch_payment_terms",
"deferred_accounting_settings_section",
@@ -114,12 +113,6 @@
"label": "Book Asset Depreciation Entry Automatically"
},
{
- "default": "0",
- "fieldname": "allow_cost_center_in_entry_of_bs_account",
- "fieldtype": "Check",
- "label": "Allow Cost Center In Entry of Balance Sheet Account"
- },
- {
"default": "1",
"fieldname": "add_taxes_from_item_tax_template",
"fieldtype": "Check",
@@ -232,7 +225,7 @@
"idx": 1,
"issingle": 1,
"links": [],
- "modified": "2020-06-22 20:13:26.043092",
+ "modified": "2020-08-03 20:13:26.043092",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index 2473d71..5593466 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -20,7 +20,6 @@
self.validate_stale_days()
self.enable_payment_schedule_in_print()
- self.enable_fields_for_cost_center_settings()
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
@@ -33,8 +32,3 @@
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check")
make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check")
-
- def enable_fields_for_cost_center_settings(self):
- show_field = 0 if cint(self.allow_cost_center_in_entry_of_bs_account) else 1
- for doctype in ("Sales Invoice", "Purchase Invoice", "Payment Entry"):
- make_property_setter(doctype, "cost_center", "hidden", show_field, "Check")
diff --git a/erpnext/accounts/doctype/bank/bank.json b/erpnext/accounts/doctype/bank/bank.json
index 99978e6..56bae72 100644
--- a/erpnext/accounts/doctype/bank/bank.json
+++ b/erpnext/accounts/doctype/bank/bank.json
@@ -13,7 +13,6 @@
"bank_name",
"swift_number",
"column_break_1",
- "branch_code",
"website",
"address_and_contact",
"address_html",
@@ -52,15 +51,6 @@
"search_index": 1
},
{
- "allow_in_quick_entry": 1,
- "fieldname": "branch_code",
- "fieldtype": "Data",
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Branch Code",
- "unique": 1
- },
- {
"fieldname": "address_and_contact",
"fieldtype": "Section Break",
"label": "Address and Contact",
@@ -111,7 +101,7 @@
}
],
"links": [],
- "modified": "2020-03-25 21:22:33.496264",
+ "modified": "2020-07-17 14:00:13.105433",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank",
diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json
index 65a0a51..b42f1f9 100644
--- a/erpnext/accounts/doctype/bank_account/bank_account.json
+++ b/erpnext/accounts/doctype/bank_account/bank_account.json
@@ -23,6 +23,7 @@
"account_details_section",
"iban",
"column_break_12",
+ "branch_code",
"bank_account_no",
"address_and_contact",
"address_html",
@@ -197,10 +198,16 @@
"fieldtype": "Data",
"label": "Mask",
"read_only": 1
+ },
+ {
+ "fieldname": "branch_code",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "label": "Branch Code"
}
],
"links": [],
- "modified": "2020-04-06 21:00:45.379804",
+ "modified": "2020-07-17 13:59:50.795412",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account",
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
index 6fec3ab..76d82e7 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
@@ -60,12 +60,12 @@
""".format(condition=condition), {"account": self.account, "from":self.from_date,
"to": self.to_date, "bank_account": self.bank_account}, as_dict=1)
- pos_entries = []
+ pos_sales_invoices, pos_purchase_invoices = [], []
if self.include_pos_transactions:
- pos_entries = frappe.db.sql("""
+ pos_sales_invoices = frappe.db.sql("""
select
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
- si.posting_date, si.debit_to as against_account, sip.clearance_date,
+ si.posting_date, si.customer as against_account, sip.clearance_date,
account.account_currency, 0 as credit
from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
where
@@ -75,7 +75,20 @@
si.posting_date ASC, si.name DESC
""", {"account":self.account, "from":self.from_date, "to":self.to_date}, as_dict=1)
- entries = sorted(list(payment_entries)+list(journal_entries+list(pos_entries)),
+ pos_purchase_invoices = frappe.db.sql("""
+ select
+ "Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
+ pi.posting_date, pi.supplier as against_account, pi.clearance_date,
+ account.account_currency, 0 as debit
+ from `tabPurchase Invoice` pi, `tabAccount` account
+ where
+ pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account
+ and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
+ order by
+ pi.posting_date ASC, pi.name DESC
+ """, {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
+
+ entries = sorted(list(payment_entries) + list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
key=lambda k: k['posting_date'] or getdate(nowdate()))
self.set('payment_entries', [])
diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js
index 065d25e..febf85c 100644
--- a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js
+++ b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js
@@ -4,7 +4,7 @@
cur_frm.add_fetch('bank_account','account','account');
cur_frm.add_fetch('bank_account','bank_account_no','bank_account_no');
cur_frm.add_fetch('bank_account','iban','iban');
-cur_frm.add_fetch('bank','branch_code','branch_code');
+cur_frm.add_fetch('bank_account','branch_code','branch_code');
cur_frm.add_fetch('bank','swift_number','swift_number');
frappe.ui.form.on('Bank Guarantee', {
diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
index 990b896..3a0d416 100644
--- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
+++ b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
@@ -26,22 +26,22 @@
"item_group": "_Test Item Group",
"item_name": "_Test Tesla Car",
"apply_warehouse_wise_reorder_level": 0,
- "warehouse":"_Test Warehouse - _TC",
+ "warehouse":"Stores - TCP1",
"gst_hsn_code": "999800",
"valuation_rate": 5000,
"standard_rate":5000,
"item_defaults": [{
- "company": "_Test Company",
- "default_warehouse": "_Test Warehouse - _TC",
+ "company": "_Test Company with perpetual inventory",
+ "default_warehouse": "Stores - TCP1",
"default_price_list":"_Test Price List",
- "expense_account": "_Test Account Cost for Goods Sold - _TC",
- "buying_cost_center": "_Test Cost Center - _TC",
- "selling_cost_center": "_Test Cost Center - _TC",
- "income_account": "Sales - _TC"
+ "expense_account": "Cost of Goods Sold - TCP1",
+ "buying_cost_center": "Main - TCP1",
+ "selling_cost_center": "Main - TCP1",
+ "income_account": "Sales - TCP1"
}],
"show_in_website": 1,
"route":"-test-tesla-car",
- "website_warehouse": "_Test Warehouse - _TC"
+ "website_warehouse": "Stores - TCP1"
})
item.insert()
# create test item price
@@ -63,12 +63,12 @@
"items": [{
"item_code": "_Test Tesla Car"
}],
- "warehouse":"_Test Warehouse - _TC",
+ "warehouse":"Stores - TCP1",
"coupon_code_based":1,
"selling": 1,
"rate_or_discount": "Discount Percentage",
"discount_percentage": 30,
- "company": "_Test Company",
+ "company": "_Test Company with perpetual inventory",
"currency":"INR",
"for_price_list":"_Test Price List"
})
@@ -112,7 +112,10 @@
self.assertEqual(coupon_code.get("used"),0)
def test_2_sales_order_with_coupon_code(self):
- so = make_sales_order(customer="_Test Customer",selling_price_list="_Test Price List",item_code="_Test Tesla Car", rate=5000,qty=1, do_not_submit=True)
+ so = make_sales_order(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1',
+ customer="_Test Customer", selling_price_list="_Test Price List", item_code="_Test Tesla Car", rate=5000,qty=1,
+ do_not_submit=True)
+
so = frappe.get_doc('Sales Order', so.name)
# check item price before coupon code is applied
self.assertEqual(so.items[0].rate, 5000)
@@ -120,7 +123,7 @@
so.sales_partner='_Test Coupon Partner'
so.save()
# check item price after coupon code is applied
- self.assertEqual(so.items[0].rate, 3500)
+ self.assertEqual(so.items[0].rate, 3500)
so.submit()
def test_3_check_coupon_code_used_after_so(self):
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/dunning/__init__.py
similarity index 100%
rename from erpnext/accounts/page/pos/__init__.py
rename to erpnext/accounts/doctype/dunning/__init__.py
diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js
new file mode 100644
index 0000000..9909c6c
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/dunning.js
@@ -0,0 +1,162 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("Dunning", {
+ setup: function (frm) {
+ frm.set_query("sales_invoice", () => {
+ return {
+ filters: {
+ docstatus: 1,
+ company: frm.doc.company,
+ outstanding_amount: [">", 0],
+ status: "Overdue"
+ },
+ };
+ });
+ frm.set_query("income_account", () => {
+ return {
+ filters: {
+ company: frm.doc.company,
+ root_type: "Income",
+ is_group: 0
+ }
+ };
+ });
+ },
+ refresh: function (frm) {
+ frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1);
+ frm.set_df_property(
+ "sales_invoice",
+ "read_only",
+ frm.doc.__islocal ? 0 : 1
+ );
+ if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") {
+ frm.add_custom_button(__("Resolve"), () => {
+ frm.set_value("status", "Resolved");
+ });
+ }
+ if (frm.doc.docstatus === 1 && frm.doc.status !== "Resolved") {
+ frm.add_custom_button(
+ __("Payment"),
+ function () {
+ frm.events.make_payment_entry(frm);
+ },__("Create")
+ );
+ frm.page.set_inner_btn_group_as_primary(__("Create"));
+ }
+
+ if(frm.doc.docstatus > 0) {
+ frm.add_custom_button(__('Ledger'), function() {
+ frappe.route_options = {
+ "voucher_no": frm.doc.name,
+ "from_date": frm.doc.posting_date,
+ "to_date": frm.doc.posting_date,
+ "company": frm.doc.company,
+ "show_cancelled_entries": frm.doc.docstatus === 2
+ };
+ frappe.set_route("query-report", "General Ledger");
+ }, __('View'));
+ }
+ },
+ overdue_days: function (frm) {
+ frappe.db.get_value(
+ "Dunning Type",
+ {
+ start_day: ["<", frm.doc.overdue_days],
+ end_day: [">=", frm.doc.overdue_days],
+ },
+ "dunning_type",
+ (r) => {
+ if (r) {
+ frm.set_value("dunning_type", r.dunning_type);
+ } else {
+ frm.set_value("dunning_type", "");
+ frm.set_value("rate_of_interest", "");
+ frm.set_value("dunning_fee", "");
+ }
+ }
+ );
+ },
+ dunning_type: function (frm) {
+ frm.trigger("get_dunning_letter_text");
+ },
+ language: function (frm) {
+ frm.trigger("get_dunning_letter_text");
+ },
+ get_dunning_letter_text: function (frm) {
+ if (frm.doc.dunning_type) {
+ frappe.call({
+ method:
+ "erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text",
+ args: {
+ dunning_type: frm.doc.dunning_type,
+ language: frm.doc.language,
+ doc: frm.doc,
+ },
+ callback: function (r) {
+ if (r.message) {
+ frm.set_value("body_text", r.message.body_text);
+ frm.set_value("closing_text", r.message.closing_text);
+ frm.set_value("language", r.message.language);
+ } else {
+ frm.set_value("body_text", "");
+ frm.set_value("closing_text", "");
+ }
+ },
+ });
+ }
+ },
+ due_date: function (frm) {
+ frm.trigger("calculate_overdue_days");
+ },
+ posting_date: function (frm) {
+ frm.trigger("calculate_overdue_days");
+ },
+ rate_of_interest: function (frm) {
+ frm.trigger("calculate_interest_and_amount");
+ },
+ outstanding_amount: function (frm) {
+ frm.trigger("calculate_interest_and_amount");
+ },
+ interest_amount: function (frm) {
+ frm.trigger("calculate_interest_and_amount");
+ },
+ dunning_fee: function (frm) {
+ frm.trigger("calculate_interest_and_amount");
+ },
+ sales_invoice: function (frm) {
+ frm.trigger("calculate_overdue_days");
+ },
+ calculate_overdue_days: function (frm) {
+ if (frm.doc.posting_date && frm.doc.due_date) {
+ const overdue_days = moment(frm.doc.posting_date).diff(
+ frm.doc.due_date,
+ "days"
+ );
+ frm.set_value("overdue_days", overdue_days);
+ }
+ },
+ calculate_interest_and_amount: function (frm) {
+ const interest_per_year = frm.doc.outstanding_amount * frm.doc.rate_of_interest / 100;
+ const interest_amount = flt((interest_per_year * cint(frm.doc.overdue_days)) / 365 || 0, precision('interest_amount'));
+ const dunning_amount = flt(interest_amount + frm.doc.dunning_fee, precision('dunning_amount'));
+ const grand_total = flt(frm.doc.outstanding_amount + dunning_amount, precision('grand_total'));
+ frm.set_value("interest_amount", interest_amount);
+ frm.set_value("dunning_amount", dunning_amount);
+ frm.set_value("grand_total", grand_total);
+ },
+ make_payment_entry: function (frm) {
+ return frappe.call({
+ method:
+ "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry",
+ args: {
+ dt: frm.doc.doctype,
+ dn: frm.doc.name,
+ },
+ callback: function (r) {
+ var doc = frappe.model.sync(r.message);
+ frappe.set_route("Form", doc[0].doctype, doc[0].name);
+ },
+ });
+ },
+});
diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json
new file mode 100644
index 0000000..d55bfd1
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/dunning.json
@@ -0,0 +1,370 @@
+{
+ "actions": [],
+ "allow_events_in_timeline": 1,
+ "autoname": "naming_series:",
+ "creation": "2019-07-05 16:34:31.013238",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "naming_series",
+ "sales_invoice",
+ "customer",
+ "customer_name",
+ "outstanding_amount",
+ "currency",
+ "conversion_rate",
+ "column_break_3",
+ "company",
+ "posting_date",
+ "posting_time",
+ "due_date",
+ "overdue_days",
+ "address_and_contact_section",
+ "address_display",
+ "contact_display",
+ "contact_mobile",
+ "contact_email",
+ "column_break_18",
+ "company_address_display",
+ "section_break_6",
+ "dunning_type",
+ "dunning_fee",
+ "column_break_8",
+ "rate_of_interest",
+ "interest_amount",
+ "section_break_12",
+ "dunning_amount",
+ "grand_total",
+ "income_account",
+ "column_break_17",
+ "status",
+ "printing_setting_section",
+ "language",
+ "body_text",
+ "column_break_22",
+ "letter_head",
+ "closing_text",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "default": "DUNN-.MM.-.YY.-",
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "options": "DUNN-.MM.-.YY.-"
+ },
+ {
+ "fieldname": "sales_invoice",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Sales Invoice",
+ "options": "Sales Invoice",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "sales_invoice.customer_name",
+ "fieldname": "customer_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Customer Name",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "sales_invoice.outstanding_amount",
+ "fieldname": "outstanding_amount",
+ "fieldtype": "Currency",
+ "label": "Outstanding Amount",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "Today",
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "label": "Date"
+ },
+ {
+ "fieldname": "overdue_days",
+ "fieldtype": "Int",
+ "label": "Overdue Days",
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "dunning_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Dunning Type",
+ "options": "Dunning Type",
+ "reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "interest_amount",
+ "fieldtype": "Currency",
+ "label": "Interest Amount",
+ "precision": "2",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fetch_from": "dunning_type.dunning_fee",
+ "fetch_if_empty": 1,
+ "fieldname": "dunning_fee",
+ "fieldtype": "Currency",
+ "label": "Dunning Fee",
+ "precision": "2"
+ },
+ {
+ "fieldname": "section_break_12",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_17",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "printing_setting_section",
+ "fieldtype": "Section Break",
+ "label": "Printing Setting"
+ },
+ {
+ "fieldname": "language",
+ "fieldtype": "Link",
+ "label": "Print Language",
+ "options": "Language"
+ },
+ {
+ "fieldname": "letter_head",
+ "fieldtype": "Link",
+ "label": "Letter Head",
+ "options": "Letter Head"
+ },
+ {
+ "fieldname": "column_break_22",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "sales_invoice.currency",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Currency",
+ "options": "Currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Dunning",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "{customer_name}",
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Title"
+ },
+ {
+ "fieldname": "body_text",
+ "fieldtype": "Text Editor",
+ "label": "Body Text"
+ },
+ {
+ "fieldname": "closing_text",
+ "fieldtype": "Text Editor",
+ "label": "Closing Text"
+ },
+ {
+ "fetch_from": "sales_invoice.due_date",
+ "fieldname": "due_date",
+ "fieldtype": "Date",
+ "label": "Due Date",
+ "read_only": 1
+ },
+ {
+ "fieldname": "posting_time",
+ "fieldtype": "Time",
+ "label": "Posting Time"
+ },
+ {
+ "default": "0",
+ "fetch_from": "dunning_type.rate_of_interest",
+ "fetch_if_empty": 1,
+ "fieldname": "rate_of_interest",
+ "fieldtype": "Float",
+ "label": "Rate of Interest (%) Yearly"
+ },
+ {
+ "fieldname": "address_and_contact_section",
+ "fieldtype": "Section Break",
+ "label": "Address and Contact"
+ },
+ {
+ "fetch_from": "sales_invoice.address_display",
+ "fieldname": "address_display",
+ "fieldtype": "Small Text",
+ "label": "Address",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "sales_invoice.contact_display",
+ "fieldname": "contact_display",
+ "fieldtype": "Small Text",
+ "label": "Contact",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "sales_invoice.contact_mobile",
+ "fieldname": "contact_mobile",
+ "fieldtype": "Small Text",
+ "label": "Mobile No",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_18",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "sales_invoice.company_address_display",
+ "fieldname": "company_address_display",
+ "fieldtype": "Small Text",
+ "label": "Company Address",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "sales_invoice.contact_email",
+ "fieldname": "contact_email",
+ "fieldtype": "Data",
+ "label": "Contact Email",
+ "options": "Email",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "sales_invoice.customer",
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "label": "Customer",
+ "options": "Customer",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "grand_total",
+ "fieldtype": "Currency",
+ "label": "Grand Total",
+ "precision": "2",
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "Unresolved",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_standard_filter": 1,
+ "label": "Status",
+ "options": "Draft\nResolved\nUnresolved\nCancelled"
+ },
+ {
+ "fieldname": "dunning_amount",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "label": "Dunning Amount",
+ "read_only": 1
+ },
+ {
+ "fieldname": "income_account",
+ "fieldtype": "Link",
+ "label": "Income Account",
+ "options": "Account"
+ },
+ {
+ "fetch_from": "sales_invoice.conversion_rate",
+ "fieldname": "conversion_rate",
+ "fieldtype": "Float",
+ "hidden": 1,
+ "label": "Conversion Rate",
+ "read_only": 1
+ }
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-08-03 18:55:43.683053",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Dunning",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "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
+ },
+ {
+ "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
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "title_field": "customer_name",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
new file mode 100644
index 0000000..3e372af
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import json
+from six import string_types
+from frappe.utils import getdate, get_datetime, rounded, flt, cint
+from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
+from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
+from erpnext.controllers.accounts_controller import AccountsController
+
+
+class Dunning(AccountsController):
+ def validate(self):
+ self.validate_overdue_days()
+ self.validate_amount()
+ if not self.income_account:
+ self.income_account = frappe.db.get_value('Company', self.company, 'default_income_account')
+
+ def validate_overdue_days(self):
+ self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0
+
+ def validate_amount(self):
+ amounts = calculate_interest_and_amount(
+ self.posting_date, self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
+ if self.interest_amount != amounts.get('interest_amount'):
+ self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount'))
+ if self.dunning_amount != amounts.get('dunning_amount'):
+ self.dunning_amount = flt(amounts.get('dunning_amount'), self.precision('dunning_amount'))
+ if self.grand_total != amounts.get('grand_total'):
+ self.grand_total = flt(amounts.get('grand_total'), self.precision('grand_total'))
+
+ def on_submit(self):
+ self.make_gl_entries()
+
+ def on_cancel(self):
+ if self.dunning_amount:
+ self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+ make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
+
+ def make_gl_entries(self):
+ if not self.dunning_amount:
+ return
+ gl_entries = []
+ invoice_fields = ["project", "cost_center", "debit_to", "party_account_currency", "conversion_rate", "cost_center"]
+ inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1)
+
+ accounting_dimensions = get_accounting_dimensions()
+ invoice_fields.extend(accounting_dimensions)
+
+ dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate)
+ default_cost_center = frappe.get_cached_value('Company', self.company, 'cost_center')
+
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": inv.debit_to,
+ "party_type": "Customer",
+ "party": self.customer,
+ "due_date": self.due_date,
+ "against": self.income_account,
+ "debit": dunning_in_company_currency,
+ "debit_in_account_currency": self.dunning_amount,
+ "against_voucher": self.name,
+ "against_voucher_type": "Dunning",
+ "cost_center": inv.cost_center or default_cost_center,
+ "project": inv.project
+ }, inv.party_account_currency, item=inv)
+ )
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": self.income_account,
+ "against": self.customer,
+ "credit": dunning_in_company_currency,
+ "cost_center": inv.cost_center or default_cost_center,
+ "credit_in_account_currency": self.dunning_amount,
+ "project": inv.project
+ }, item=inv)
+ )
+ make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False)
+
+
+def resolve_dunning(doc, state):
+ for reference in doc.references:
+ if reference.reference_doctype == 'Sales Invoice' and reference.outstanding_amount <= 0:
+ dunnings = frappe.get_list('Dunning', filters={
+ 'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')})
+
+ for dunning in dunnings:
+ frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved')
+
+def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
+ interest_amount = 0
+ if rate_of_interest:
+ interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
+ interest_amount = (interest_per_year * cint(overdue_days)) / 365
+ grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee)
+ dunning_amount = flt(interest_amount) + flt(dunning_fee)
+ return {
+ 'interest_amount': interest_amount,
+ 'grand_total': grand_total,
+ 'dunning_amount': dunning_amount}
+
+@frappe.whitelist()
+def get_dunning_letter_text(dunning_type, doc, language=None):
+ if isinstance(doc, string_types):
+ doc = json.loads(doc)
+ if language:
+ filters = {'parent': dunning_type, 'language': language}
+ else:
+ filters = {'parent': dunning_type, 'is_default_language': 1}
+ letter_text = frappe.db.get_value('Dunning Letter Text', filters,
+ ['body_text', 'closing_text', 'language'], as_dict=1)
+ if letter_text:
+ return {
+ 'body_text': frappe.render_template(letter_text.body_text, doc),
+ 'closing_text': frappe.render_template(letter_text.closing_text, doc),
+ 'language': letter_text.language
+ }
diff --git a/erpnext/accounts/doctype/dunning/dunning_dashboard.py b/erpnext/accounts/doctype/dunning/dunning_dashboard.py
new file mode 100644
index 0000000..19a73dd
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/dunning_dashboard.py
@@ -0,0 +1,17 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'dunning',
+ 'non_standard_fieldnames': {
+ 'Journal Entry': 'reference_name',
+ 'Payment Entry': 'reference_name'
+ },
+ 'transactions': [
+ {
+ 'label': _('Payment'),
+ 'items': ['Payment Entry', 'Journal Entry']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/dunning/dunning_list.js b/erpnext/accounts/doctype/dunning/dunning_list.js
new file mode 100644
index 0000000..8dc0a8c
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/dunning_list.js
@@ -0,0 +1,9 @@
+frappe.listview_settings["Dunning"] = {
+ get_indicator: function (doc) {
+ if (doc.status === "Resolved") {
+ return [__("Resolved"), "green", "status,=,Resolved"];
+ } else {
+ return [__("Unresolved"), "red", "status,=,Unresolved"];
+ }
+ },
+};
diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py
new file mode 100644
index 0000000..cb18309
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/test_dunning.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+from frappe.utils import add_days, today, nowdate
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice_against_cost_center
+from erpnext.accounts.doctype.dunning.dunning import calculate_interest_and_amount
+from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+
+
+class TestDunning(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ create_dunning_type()
+ unlink_payment_on_cancel_of_invoice()
+
+ @classmethod
+ def tearDownClass(self):
+ unlink_payment_on_cancel_of_invoice(0)
+
+ def test_dunning(self):
+ dunning = create_dunning()
+ amounts = calculate_interest_and_amount(
+ dunning.posting_date, dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
+ self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
+ self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44)
+ self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
+
+ def test_gl_entries(self):
+ dunning = create_dunning()
+ dunning.submit()
+ gl_entries = frappe.db.sql("""select account, debit, credit
+ from `tabGL Entry` where voucher_type='Dunning' and voucher_no=%s
+ order by account asc""", dunning.name, as_dict=1)
+ self.assertTrue(gl_entries)
+ expected_values = dict((d[0], d) for d in [
+ ['Debtors - _TC', 20.44, 0.0],
+ ['Sales - _TC', 0.0, 20.44]
+ ])
+ for gle in gl_entries:
+ self.assertEquals(expected_values[gle.account][0], gle.account)
+ self.assertEquals(expected_values[gle.account][1], gle.debit)
+ self.assertEquals(expected_values[gle.account][2], gle.credit)
+
+ def test_payment_entry(self):
+ dunning = create_dunning()
+ dunning.submit()
+ pe = get_payment_entry("Dunning", dunning.name)
+ pe.reference_no = "1"
+ pe.reference_date = nowdate()
+ pe.paid_from_account_currency = dunning.currency
+ pe.paid_to_account_currency = dunning.currency
+ pe.source_exchange_rate = 1
+ pe.target_exchange_rate = 1
+ pe.insert()
+ pe.submit()
+ si_doc = frappe.get_doc('Sales Invoice', dunning.sales_invoice)
+ self.assertEqual(si_doc.outstanding_amount, 0)
+
+
+def create_dunning():
+ posting_date = add_days(today(), -20)
+ due_date = add_days(today(), -15)
+ sales_invoice = create_sales_invoice_against_cost_center(
+ posting_date=posting_date, due_date=due_date, status='Overdue')
+ dunning_type = frappe.get_doc("Dunning Type", 'First Notice')
+ dunning = frappe.new_doc("Dunning")
+ dunning.sales_invoice = sales_invoice.name
+ dunning.customer_name = sales_invoice.customer_name
+ dunning.outstanding_amount = sales_invoice.outstanding_amount
+ dunning.debit_to = sales_invoice.debit_to
+ dunning.currency = sales_invoice.currency
+ dunning.company = sales_invoice.company
+ dunning.posting_date = nowdate()
+ dunning.due_date = sales_invoice.due_date
+ dunning.dunning_type = 'First Notice'
+ dunning.rate_of_interest = dunning_type.rate_of_interest
+ dunning.dunning_fee = dunning_type.dunning_fee
+ dunning.save()
+ return dunning
+
+def create_dunning_type():
+ dunning_type = frappe.new_doc("Dunning Type")
+ dunning_type.dunning_type = 'First Notice'
+ dunning_type.start_day = 10
+ dunning_type.end_day = 20
+ dunning_type.dunning_fee = 20
+ dunning_type.rate_of_interest = 8
+ dunning_type.append(
+ "dunning_letter_text", {
+ 'language': 'en',
+ 'body_text': 'We have still not received payment for our invoice ',
+ 'closing_text': 'We kindly request that you pay the outstanding amount immediately, including interest and late fees.'
+ }
+ )
+ dunning_type.save()
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/dunning_letter_text/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/dunning_letter_text/__init__.py
diff --git a/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json
new file mode 100644
index 0000000..5ede3a1
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json
@@ -0,0 +1,70 @@
+{
+ "actions": [],
+ "creation": "2019-12-06 04:25:40.215625",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "language",
+ "is_default_language",
+ "section_break_4",
+ "body_text",
+ "closing_text",
+ "section_break_7",
+ "body_and_closing_text_help"
+ ],
+ "fields": [
+ {
+ "fieldname": "language",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Language",
+ "options": "Language"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_default_language",
+ "fieldtype": "Check",
+ "label": "Is Default Language"
+ },
+ {
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break"
+ },
+ {
+ "description": "Letter or Email Body Text",
+ "fieldname": "body_text",
+ "fieldtype": "Text Editor",
+ "in_list_view": 1,
+ "label": "Body Text"
+ },
+ {
+ "description": "Letter or Email Closing Text",
+ "fieldname": "closing_text",
+ "fieldtype": "Text Editor",
+ "in_list_view": 1,
+ "label": "Closing Text"
+ },
+ {
+ "fieldname": "section_break_7",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "body_and_closing_text_help",
+ "fieldtype": "HTML",
+ "label": "Body and Closing Text Help",
+ "options": "<h4>Body Text and Closing Text Example</h4>\n\n<div>We have noticed that you have not yet paid invoice {{sales_invoice}} for {{frappe.db.get_value(\"Currency\", currency, \"symbol\")}} {{outstanding_amount}}. This is a friendly reminder that the invoice was due on {{due_date}}. Please pay the amount due immediately to avoid any further dunning cost.</div>\n\n<h4>How to get fieldnames</h4>\n\n<p>The fieldnames you can use in your template are the fields in the document. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-07-14 18:02:35.988958",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Dunning Letter Text",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.py b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.py
new file mode 100644
index 0000000..426497b
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class DunningLetterText(Document):
+ pass
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/dunning_type/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/dunning_type/__init__.py
diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.js b/erpnext/accounts/doctype/dunning_type/dunning_type.js
new file mode 100644
index 0000000..54156b4
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_type/dunning_type.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Dunning Type', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.json b/erpnext/accounts/doctype/dunning_type/dunning_type.json
new file mode 100644
index 0000000..da43664
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_type/dunning_type.json
@@ -0,0 +1,129 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:dunning_type",
+ "creation": "2019-12-04 04:59:08.003664",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "dunning_type",
+ "overdue_interval_section",
+ "start_day",
+ "column_break_4",
+ "end_day",
+ "section_break_6",
+ "dunning_fee",
+ "column_break_8",
+ "rate_of_interest",
+ "text_block_section",
+ "dunning_letter_text"
+ ],
+ "fields": [
+ {
+ "fieldname": "dunning_type",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Dunning Type",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "dunning_fee",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Dunning Fee"
+ },
+ {
+ "description": "This section allows the user to set the Body and Closing text of the Dunning Letter for the Dunning Type based on language, which can be used in Print.",
+ "fieldname": "text_block_section",
+ "fieldtype": "Section Break",
+ "label": "Dunning Letter"
+ },
+ {
+ "fieldname": "dunning_letter_text",
+ "fieldtype": "Table",
+ "options": "Dunning Letter Text"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "overdue_interval_section",
+ "fieldtype": "Section Break",
+ "label": "Overdue Interval"
+ },
+ {
+ "fieldname": "start_day",
+ "fieldtype": "Int",
+ "label": "Start Day"
+ },
+ {
+ "fieldname": "end_day",
+ "fieldtype": "Int",
+ "label": "End Day"
+ },
+ {
+ "fieldname": "rate_of_interest",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Rate of Interest (%) Yearly"
+ }
+ ],
+ "links": [],
+ "modified": "2020-07-15 17:14:17.835074",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Dunning Type",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Administrator",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.py b/erpnext/accounts/doctype/dunning_type/dunning_type.py
similarity index 60%
rename from erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.py
rename to erpnext/accounts/doctype/dunning_type/dunning_type.py
index a2d488b..8708748 100644
--- a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.py
+++ b/erpnext/accounts/doctype/dunning_type/dunning_type.py
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
+# import frappe
from frappe.model.document import Document
-class POSClosingVoucherInvoices(Document):
+class DunningType(Document):
pass
diff --git a/erpnext/accounts/doctype/dunning_type/test_dunning_type.py b/erpnext/accounts/doctype/dunning_type/test_dunning_type.py
new file mode 100644
index 0000000..b2fb26f
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_type/test_dunning_type.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestDunningType(unittest.TestCase):
+ pass
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 645da34..def9ed6 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -72,12 +72,6 @@
if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.")
.format(self.voucher_type, self.voucher_no, self.account))
- else:
- from erpnext.accounts.utils import get_allow_cost_center_in_entry_of_bs_account
- if not get_allow_cost_center_in_entry_of_bs_account() and self.cost_center:
- self.cost_center = None
- if self.project:
- self.project = None
def validate_dimensions_for_pl_and_bs(self):
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
index 594b4d4..8083b21 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
@@ -3,7 +3,7 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe, json
+import frappe, json, erpnext
from frappe import _
from frappe.utils import flt, getdate, nowdate, add_days
from erpnext.controllers.accounts_controller import AccountsController
@@ -134,16 +134,19 @@
je.append("accounts", {
"account": self.bank_account,
"debit_in_account_currency": flt(self.total_amount) - flt(self.bank_charges),
+ "cost_center": erpnext.get_default_cost_center(self.company)
})
je.append("accounts", {
"account": self.bank_charges_account,
- "debit_in_account_currency": flt(self.bank_charges)
+ "debit_in_account_currency": flt(self.bank_charges),
+ "cost_center": erpnext.get_default_cost_center(self.company)
})
je.append("accounts", {
"account": self.short_term_loan,
"credit_in_account_currency": flt(self.total_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name
})
@@ -151,6 +154,7 @@
je.append("accounts", {
"account": self.accounts_receivable_discounted,
"debit_in_account_currency": flt(d.outstanding_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
@@ -160,6 +164,7 @@
je.append("accounts", {
"account": self.accounts_receivable_credit,
"credit_in_account_currency": flt(d.outstanding_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
@@ -177,13 +182,15 @@
je.append("accounts", {
"account": self.short_term_loan,
"debit_in_account_currency": flt(self.total_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
})
je.append("accounts", {
"account": self.bank_account,
- "credit_in_account_currency": flt(self.total_amount)
+ "credit_in_account_currency": flt(self.total_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company)
})
if getdate(self.loan_end_date) > getdate(nowdate()):
@@ -193,6 +200,7 @@
je.append("accounts", {
"account": self.accounts_receivable_discounted,
"credit_in_account_currency": flt(outstanding_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
@@ -202,6 +210,7 @@
je.append("accounts", {
"account": self.accounts_receivable_unpaid,
"debit_in_account_currency": flt(outstanding_amount),
+ "cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting",
"reference_name": self.name,
"party_type": "Customer",
diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.js b/erpnext/accounts/doctype/item_tax_template/item_tax_template.js
index 42abdb8..e921a0d 100644
--- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.js
+++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.js
@@ -6,6 +6,18 @@
frm.set_query("tax_type", "taxes", function(doc) {
return {
filters: [
+ ['Account', 'company', '=', frm.doc.company],
+ ['Account', 'is_group', '=', 0],
+ ['Account', 'account_type', 'in', ['Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation']]
+ ]
+ }
+ });
+ },
+ company: function (frm) {
+ frm.set_query("tax_type", "taxes", function(doc) {
+ return {
+ filters: [
+ ['Account', 'company', '=', frm.doc.company],
['Account', 'is_group', '=', 0],
['Account', 'account_type', 'in', ['Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation']]
]
diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
index f713cfc..856c371 100644
--- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
+++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
@@ -1,168 +1,85 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:title",
- "beta": 0,
- "creation": "2018-11-22 22:45:00.370913",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:title",
+ "creation": "2018-11-22 22:45:00.370913",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "company",
+ "taxes"
+ ],
"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": 1,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 1,
- "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,
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_filter": 1,
+ "in_list_view": 1,
+ "label": "Title",
+ "no_copy": 1,
+ "reqd": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "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_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Tax Rates",
- "length": 0,
- "no_copy": 0,
- "options": "Item Tax Template Detail",
- "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": "taxes",
+ "fieldtype": "Table",
+ "label": "Tax Rates",
+ "options": "Item Tax Template Detail",
+ "reqd": 1
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 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-12-21 23:51:16.328340",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Item Tax Template",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "modified": "2020-06-18 20:27:42.615842",
+ "modified_by": "ahmad@havenir.com",
+ "module": "Accounts",
+ "name": "Item Tax Template",
+ "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": 0,
- "delete": 0,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 0
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "",
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/item_tax_template/test_records.json b/erpnext/accounts/doctype/item_tax_template/test_records.json
index db540e8..4d9537d 100644
--- a/erpnext/accounts/doctype/item_tax_template/test_records.json
+++ b/erpnext/accounts/doctype/item_tax_template/test_records.json
@@ -2,6 +2,7 @@
{
"doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 10",
+ "company": "_Test Company",
"taxes": [
{
"doctype": "Item Tax Template Detail",
@@ -14,6 +15,7 @@
{
"doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 12",
+ "company": "_Test Company",
"taxes": [
{
"doctype": "Item Tax Template Detail",
@@ -26,6 +28,7 @@
{
"doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 15",
+ "company": "_Test Company",
"taxes": [
{
"doctype": "Item Tax Template Detail",
@@ -38,6 +41,7 @@
{
"doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 20",
+ "company": "_Test Company",
"taxes": [
{
"doctype": "Item Tax Template Detail",
@@ -50,6 +54,7 @@
{
"doctype": "Item Tax Template",
"title": "_Test Item Tax Template 1",
+ "company": "_Test Company",
"taxes": [
{
"doctype": "Item Tax Template Detail",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 7360b39..dda1708 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -840,13 +840,34 @@
return [{"account": a, "balance": get_balance_on(a)} for a in accounts]
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark
- from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
- where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s
- and (jv_detail.reference_type is null or jv_detail.reference_type = '')
- and jv.docstatus = 1 and jv.`{0}` like %s order by jv.name desc limit %s, %s""".format(searchfield),
- (filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len))
+ if not frappe.db.has_column('Journal Entry', searchfield):
+ return []
+
+ return frappe.db.sql("""
+ SELECT jv.name, jv.posting_date, jv.user_remark
+ FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
+ WHERE jv_detail.parent = jv.name
+ AND jv_detail.account = %(account)s
+ AND IFNULL(jv_detail.party, '') = %(party)s
+ AND (
+ jv_detail.reference_type IS NULL
+ OR jv_detail.reference_type = ''
+ )
+ AND jv.docstatus = 1
+ AND jv.`{0}` LIKE %(txt)s
+ ORDER BY jv.name DESC
+ LIMIT %(offset)s, %(limit)s
+ """.format(searchfield), dict(
+ account=filters.get("account"),
+ party=cstr(filters.get("party")),
+ txt="%{0}%".format(txt),
+ offset=start,
+ limit=page_len
+ )
+ )
@frappe.whitelist()
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 6996c77..479d4b6 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -6,6 +6,7 @@
from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.exceptions import InvalidAccountCurrency
+from erpnext.accounts.general_ledger import StockAccountInvalidTransaction
class TestJournalEntry(unittest.TestCase):
def test_journal_entry_with_against_jv(self):
@@ -81,19 +82,46 @@
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
set_perpetual_inventory()
- jv = frappe.copy_doc(test_records[0])
+ jv = frappe.copy_doc({
+ "cheque_date": nowdate(),
+ "cheque_no": "33",
+ "company": "_Test Company with perpetual inventory",
+ "doctype": "Journal Entry",
+ "accounts": [
+ {
+ "account": "Debtors - TCP1",
+ "party_type": "Customer",
+ "party": "_Test Customer",
+ "credit_in_account_currency": 400.0,
+ "debit_in_account_currency": 0.0,
+ "doctype": "Journal Entry Account",
+ "parentfield": "accounts",
+ "cost_center": "Main - TCP1"
+ },
+ {
+ "account": "_Test Bank - TCP1",
+ "credit_in_account_currency": 0.0,
+ "debit_in_account_currency": 400.0,
+ "doctype": "Journal Entry Account",
+ "parentfield": "accounts",
+ "cost_center": "Main - TCP1"
+ }
+ ],
+ "naming_series": "_T-Journal Entry-",
+ "posting_date": nowdate(),
+ "user_remark": "test",
+ "voucher_type": "Bank Entry"
+ })
+
jv.get("accounts")[0].update({
- "account": get_inventory_account('_Test Company'),
- "company": "_Test Company",
+ "account": get_inventory_account('_Test Company with perpetual inventory'),
+ "company": "_Test Company with perpetual inventory",
"party_type": None,
"party": None
})
- jv.insert()
-
- from erpnext.accounts.general_ledger import StockAccountInvalidTransaction
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
-
+ jv.cancel()
set_perpetual_inventory(0)
def test_multi_currency(self):
@@ -204,11 +232,8 @@
self.assertEqual(jv.inter_company_journal_entry_reference, "")
self.assertEqual(jv1.inter_company_journal_entry_reference, "")
- def test_jv_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ def test_jv_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False)
@@ -237,15 +262,45 @@
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
+ def test_jv_with_project(self):
+ from erpnext.projects.doctype.project.test_project import make_project
+ project = make_project({
+ 'project_name': 'Journal Entry Project',
+ 'project_template_name': 'Test Project Template',
+ 'start_date': '2020-01-01'
+ })
- def test_jv_account_and_party_balance_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
+ for d in jv.accounts:
+ d.project = project.project_name
+ jv.voucher_type = "Bank Entry"
+ jv.multi_currency = 0
+ jv.cheque_no = "112233"
+ jv.cheque_date = nowdate()
+ jv.insert()
+ jv.submit()
+
+ expected_values = {
+ "_Test Cash - _TC": {
+ "project": project.project_name
+ },
+ "_Test Bank - _TC": {
+ "project": project.project_name
+ }
+ }
+
+ gl_entries = frappe.db.sql("""select account, project, debit, credit
+ from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
+ order by account asc""", jv.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(expected_values[gle.account]["project"], gle.project)
+
+ def test_jv_account_and_party_balance_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.utils import get_balance_on
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False)
@@ -261,9 +316,6 @@
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
self.assertEqual(expected_account_balance, account_balance)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
-
def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False, project=None):
if not cost_center:
cost_center = "_Test Cost Center - _TC"
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.json b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.json
index 5975198..4c1be65 100644
--- a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.json
+++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.json
@@ -1,426 +1,123 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2018-01-23 05:40:18.117583",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "creation": "2018-01-23 05:40:18.117583",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "loyalty_program",
+ "loyalty_program_tier",
+ "customer",
+ "invoice_type",
+ "invoice",
+ "redeem_against",
+ "loyalty_points",
+ "purchase_amount",
+ "expiry_date",
+ "posting_date",
+ "company"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "loyalty_program",
- "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": "Loyalty Program",
- "length": 0,
- "no_copy": 0,
- "options": "Loyalty Program",
- "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": "loyalty_program",
+ "fieldtype": "Link",
+ "label": "Loyalty Program",
+ "options": "Loyalty Program"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "loyalty_program_tier",
- "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": "Loyalty Program Tier",
- "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": "loyalty_program_tier",
+ "fieldtype": "Data",
+ "label": "Loyalty Program Tier"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer",
- "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": "Customer",
- "length": 0,
- "no_copy": 0,
- "options": "Customer",
- "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": "customer",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Customer",
+ "options": "Customer"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_invoice",
- "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": "Sales Invoice",
- "length": 0,
- "no_copy": 0,
- "options": "Sales Invoice",
- "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": "redeem_against",
+ "fieldtype": "Link",
+ "label": "Redeem Against",
+ "options": "Loyalty Point Entry"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "redeem_against",
- "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": "Redeem Against",
- "length": 0,
- "no_copy": 0,
- "options": "Loyalty Point Entry",
- "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": "loyalty_points",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Loyalty Points"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "loyalty_points",
- "fieldtype": "Int",
- "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": "Loyalty Points",
- "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": "purchase_amount",
+ "fieldtype": "Currency",
+ "label": "Purchase Amount"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "purchase_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": "Purchase Amount",
- "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": "expiry_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Expiry Date"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expiry_date",
- "fieldtype": "Date",
- "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": "Expiry 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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "label": "Posting Date"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "posting_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": "Posting 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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company"
+ },
{
- "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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "invoice_type",
+ "fieldtype": "Link",
+ "label": "Invoice Type",
+ "options": "DocType"
+ },
+ {
+ "fieldname": "invoice",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Invoice",
+ "options": "invoice_type"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 1,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-29 16:05:22.810347",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Loyalty Point Entry",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "in_create": 1,
+ "modified": "2020-01-30 17:27:55.964242",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Loyalty Point Entry",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Auditor",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Auditor"
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "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": 0,
- "submit": 0,
- "write": 0
- },
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager"
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "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": 0,
- "submit": 0,
- "write": 0
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User"
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "customer",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "customer",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
index d65a7d8..3579a1a 100644
--- a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
+++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
@@ -18,7 +18,7 @@
date = today()
return frappe.db.sql('''
- select name, loyalty_points, expiry_date, loyalty_program_tier, sales_invoice
+ select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice
from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s
and expiry_date>=%s and loyalty_points>0 and company=%s
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
index 563165b..cb753a3 100644
--- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
@@ -36,7 +36,8 @@
return {"loyalty_points": 0, "total_spent": 0}
@frappe.whitelist()
-def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0):
+def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, \
+ silent=False, include_expired_entry=False, current_transaction_amount=0):
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
@@ -59,10 +60,10 @@
if not loyalty_program:
loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program")
- if not (loyalty_program or silent):
+ if not loyalty_program and not silent:
frappe.throw(_("Customer isn't enrolled in any Loyalty Program"))
elif silent and not loyalty_program:
- return frappe._dict({"loyalty_program": None})
+ return frappe._dict({"loyalty_programs": None})
if not company:
company = frappe.db.get_default("company") or frappe.get_all("Company")[0].name
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
index 341884c..ee73cca 100644
--- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
@@ -27,7 +27,7 @@
customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
earned_points = get_points_earned(si_original)
- lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer})
+ lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program)
self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier)
@@ -42,8 +42,8 @@
earned_after_redemption = get_points_earned(si_redeem)
- lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'redeem_against': lpe.name})
- lpe_earn = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]})
+ lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'redeem_against': lpe.name})
+ lpe_earn = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]})
self.assertEqual(lpe_earn.loyalty_points, earned_after_redemption)
self.assertEqual(lpe_redeem.loyalty_points, (-1*earned_points))
@@ -66,7 +66,7 @@
earned_points = get_points_earned(si_original)
- lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer})
+ lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program)
self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier)
@@ -82,8 +82,8 @@
customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
earned_after_redemption = get_points_earned(si_redeem)
- lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'redeem_against': lpe.name})
- lpe_earn = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]})
+ lpe_redeem = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'redeem_against': lpe.name})
+ lpe_earn = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_redeem.name, 'name': ['!=', lpe_redeem.name]})
self.assertEqual(lpe_earn.loyalty_points, earned_after_redemption)
self.assertEqual(lpe_redeem.loyalty_points, (-1*earned_points))
@@ -101,7 +101,7 @@
si.insert()
si.submit()
- lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si.name, 'customer': si.customer})
+ lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si.name, 'customer': si.customer})
self.assertEqual(True, not (lpe is None))
# cancelling sales invoice
@@ -118,7 +118,7 @@
si_original.submit()
earned_points = get_points_earned(si_original)
- lpe_original = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer})
+ lpe_original = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
self.assertEqual(lpe_original.loyalty_points, earned_points)
# create sales invoice return
@@ -130,10 +130,10 @@
si_return.submit()
# fetch original invoice again as its status would have been updated
- si_original = frappe.get_doc('Sales Invoice', lpe_original.sales_invoice)
+ si_original = frappe.get_doc('Sales Invoice', lpe_original.invoice)
earned_points = get_points_earned(si_original)
- lpe_after_return = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer})
+ lpe_after_return = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'Sales Invoice', 'invoice': si_original.name, 'customer': si_original.customer})
self.assertEqual(lpe_after_return.loyalty_points, earned_points)
self.assertEqual(True, (lpe_original.loyalty_points > lpe_after_return.loyalty_points))
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 54464e7..a53417e 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
@@ -68,6 +68,9 @@
if not self.company:
frappe.throw(_("Please select the Company"))
+ company_details = frappe.get_cached_value('Company', self.company,
+ ["default_currency", "default_letter_head"], as_dict=1) or {}
+
for row in self.invoices:
if not row.qty:
row.qty = 1.0
@@ -99,6 +102,12 @@
if not args:
continue
+ if company_details:
+ args.update({
+ "currency": company_details.get("default_currency"),
+ "letter_head": company_details.get("default_letter_head")
+ })
+
doc = frappe.get_doc(args).insert()
doc.submit()
names.append(doc.name)
@@ -172,8 +181,7 @@
"due_date": row.due_date,
"posting_date": row.posting_date,
frappe.scrub(party_type): row.party,
- "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
- "currency": frappe.get_cached_value('Company', self.company, "default_currency")
+ "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
})
accounting_dimension = get_accounting_dimensions()
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 42c9fde..4bbf63b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -90,7 +90,7 @@
frm.set_query("reference_doctype", "references", function() {
if (frm.doc.party_type=="Customer") {
- var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry"];
+ var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
} else if (frm.doc.party_type=="Supplier") {
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
} else if (frm.doc.party_type=="Employee") {
@@ -125,7 +125,7 @@
const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company};
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
- 'Purchase Order', 'Expense Claim', 'Fees'];
+ 'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
if (in_list(party_type_doctypes, child.reference_doctype)) {
filters[doc.party_type.toLowerCase()] = doc.party;
@@ -863,10 +863,10 @@
}
if(frm.doc.party_type=="Customer" &&
- !in_list(["Sales Order", "Sales Invoice", "Journal Entry"], row.reference_doctype)
+ !in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
- frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice or Journal Entry", [row.idx]));
+ frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice, Journal Entry or Dunning", [row.idx]));
return false;
}
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 59611bc..9df8655 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -6,7 +6,7 @@
import frappe, erpnext, json
from frappe import _, scrub, ValidationError
from frappe.utils import flt, comma_or, nowdate, getdate
-from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on, get_allow_cost_center_in_entry_of_bs_account
+from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
from erpnext.accounts.party import get_party_account
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.setup.utils import get_exchange_rate
@@ -199,8 +199,8 @@
def validate_account_type(self, account, account_types):
account_type = frappe.db.get_value("Account", account, "account_type")
- if account_type not in account_types:
- frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
+ # if account_type not in account_types:
+ # frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
def set_exchange_rate(self):
if self.paid_from and not self.source_exchange_rate:
@@ -223,7 +223,7 @@
if self.party_type == "Student":
valid_reference_doctypes = ("Fees")
elif self.party_type == "Customer":
- valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry")
+ valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
elif self.party_type == "Supplier":
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
elif self.party_type == "Employee":
@@ -658,7 +658,7 @@
.format(frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"]))
# Add cost center condition
- if args.get("cost_center") and get_allow_cost_center_in_entry_of_bs_account():
+ if args.get("cost_center"):
condition += " and cost_center='%s'" % args.get("cost_center")
date_fields_dict = {
@@ -897,6 +897,10 @@
total_amount = ref_doc.get("grand_total")
exchange_rate = 1
outstanding_amount = ref_doc.get("outstanding_amount")
+ if reference_doctype == "Dunning":
+ total_amount = ref_doc.get("dunning_amount")
+ exchange_rate = 1
+ outstanding_amount = ref_doc.get("dunning_amount")
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
@@ -907,7 +911,7 @@
elif reference_doctype != "Journal Entry":
if party_account_currency == company_currency:
if ref_doc.doctype == "Expense Claim":
- total_amount = ref_doc.total_sanctioned_amount
+ total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
total_amount = ref_doc.advance_amount
else:
@@ -925,8 +929,8 @@
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
- outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) \
- - flt(ref_doc.get("total_amount+reimbursed")) - flt(ref_doc.get("total_advance_amount"))
+ outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
+ - flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
elif reference_doctype == "Employee Advance":
outstanding_amount = ref_doc.advance_amount - flt(ref_doc.paid_amount)
else:
@@ -951,7 +955,7 @@
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
- if dt in ("Sales Invoice", "Sales Order"):
+ if dt in ("Sales Invoice", "Sales Order", "Dunning"):
party_type = "Customer"
elif dt in ("Purchase Invoice", "Purchase Order"):
party_type = "Supplier"
@@ -980,7 +984,7 @@
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)) \
+ if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
payment_type = "Receive"
else:
@@ -1006,6 +1010,9 @@
elif dt == "Fees":
grand_total = doc.grand_total
outstanding_amount = doc.outstanding_amount
+ elif dt == "Dunning":
+ grand_total = doc.grand_total
+ outstanding_amount = doc.grand_total
else:
if party_account_currency == doc.company_currency:
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
@@ -1029,14 +1036,14 @@
if bank_amount:
received_amount = bank_amount
else:
- received_amount = paid_amount * doc.conversion_rate
+ received_amount = paid_amount * doc.get('conversion_rate', 1)
else:
received_amount = abs(outstanding_amount)
if bank_amount:
paid_amount = bank_amount
else:
# if party account currency and bank currency is different then populate paid amount as well
- paid_amount = received_amount * doc.conversion_rate
+ paid_amount = received_amount * doc.get('conversion_rate', 1)
pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type
@@ -1075,15 +1082,35 @@
for reference in get_reference_as_per_payment_terms(doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
pe.append('references', reference)
else:
- pe.append("references", {
- 'reference_doctype': dt,
- 'reference_name': dn,
- "bill_no": doc.get("bill_no"),
- "due_date": doc.get("due_date"),
- 'total_amount': grand_total,
- 'outstanding_amount': outstanding_amount,
- 'allocated_amount': outstanding_amount
- })
+ if dt == "Dunning":
+ pe.append("references", {
+ 'reference_doctype': 'Sales Invoice',
+ 'reference_name': doc.get('sales_invoice'),
+ "bill_no": doc.get("bill_no"),
+ "due_date": doc.get("due_date"),
+ 'total_amount': doc.get('outstanding_amount'),
+ 'outstanding_amount': doc.get('outstanding_amount'),
+ 'allocated_amount': doc.get('outstanding_amount')
+ })
+ pe.append("references", {
+ 'reference_doctype': dt,
+ 'reference_name': dn,
+ "bill_no": doc.get("bill_no"),
+ "due_date": doc.get("due_date"),
+ 'total_amount': doc.get('dunning_amount'),
+ 'outstanding_amount': doc.get('dunning_amount'),
+ 'allocated_amount': doc.get('dunning_amount')
+ })
+ else:
+ pe.append("references", {
+ 'reference_doctype': dt,
+ 'reference_name': dn,
+ "bill_no": doc.get("bill_no"),
+ "due_date": doc.get("due_date"),
+ 'total_amount': grand_total,
+ 'outstanding_amount': outstanding_amount,
+ 'allocated_amount': outstanding_amount
+ })
pe.setup_party_account_field()
pe.set_missing_values()
@@ -1172,4 +1199,4 @@
}, target_doc, set_missing_values)
- return doclist
+ return doclist
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 8bb741f..772fc1a 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -460,11 +460,8 @@
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 0)
- def test_payment_entry_against_sales_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ def test_payment_entry_against_sales_invoice_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
@@ -499,39 +496,8 @@
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
-
- def test_payment_entry_against_sales_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
- si = create_sales_invoice(debit_to="Debtors - _TC")
-
- pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
-
- pe.reference_no = "112211-2"
- pe.reference_date = nowdate()
- pe.paid_to = "_Test Bank - _TC"
- pe.paid_amount = si.grand_total
- pe.insert()
- pe.submit()
-
- gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
- debit_in_account_currency, credit_in_account_currency
- from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
- order by account asc""", pe.name, as_dict=1)
-
- self.assertTrue(gl_entries)
-
- for gle in gl_entries:
- self.assertEqual(gle.cost_center, None)
-
- def test_payment_entry_against_purchase_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ def test_payment_entry_against_purchase_invoice_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
@@ -566,40 +532,9 @@
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
-
- def test_payment_entry_against_purchase_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
- pi = make_purchase_invoice(credit_to="Creditors - _TC")
-
- pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
-
- pe.reference_no = "112222-2"
- pe.reference_date = nowdate()
- pe.paid_from = "_Test Bank - _TC"
- pe.paid_amount = pi.grand_total
- pe.insert()
- pe.submit()
-
- gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
- debit_in_account_currency, credit_in_account_currency
- from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
- order by account asc""", pe.name, as_dict=1)
-
- self.assertTrue(gl_entries)
-
- for gle in gl_entries:
- self.assertEqual(gle.cost_center, None)
-
- def test_payment_entry_account_and_party_balance_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ def test_payment_entry_account_and_party_balance_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.utils import get_balance_on
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
@@ -630,9 +565,6 @@
self.assertEqual(expected_party_balance, party_balance)
self.assertEqual(expected_party_account_balance, party_account_balance)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
-
def create_payment_terms_template():
create_payment_term('Basic Amount Receivable')
@@ -665,4 +597,4 @@
frappe.get_doc({
'doctype': 'Payment Term',
'payment_term_name': name
- }).insert()
\ No newline at end of file
+ }).insert()
diff --git a/erpnext/accounts/doctype/payment_order/payment_order.py b/erpnext/accounts/doctype/payment_order/payment_order.py
index 7ecdc41..e5880aa 100644
--- a/erpnext/accounts/doctype/payment_order/payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/payment_order.py
@@ -26,6 +26,8 @@
for d in self.references:
frappe.db.set_value(self.payment_order_type, d.get(frappe.scrub(self.payment_order_type)), ref_field, status)
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(""" select mode_of_payment from `tabPayment Order Reference`
where parent = %(parent)s and mode_of_payment like %(txt)s
@@ -36,6 +38,8 @@
'txt': "%%%s%%" % txt
})
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(""" select supplier from `tabPayment Order Reference`
where parent = %(parent)s and supplier like %(txt)s and
@@ -86,4 +90,4 @@
je.flags.ignore_mandatory = True
je.save()
- frappe.msgprint(_("{0} {1} created").format(je.doctype, je.name))
\ No newline at end of file
+ frappe.msgprint(_("{0} {1} created").format(je.doctype, je.name))
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index d3992d5..355fe96 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -73,6 +73,10 @@
};
}
});
+
+ this.frm.set_value('party_type', '');
+ this.frm.set_value('party', '');
+ this.frm.set_value('receivable_payable_account', '');
},
refresh: function() {
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 3080496..2f8b634 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -48,7 +48,8 @@
select
"Journal Entry" as reference_type, t1.name as reference_name,
t1.posting_date, t1.remark as remarks, t2.name as reference_row,
- {dr_or_cr} as amount, t2.is_advance
+ {dr_or_cr} as amount, t2.is_advance,
+ t2.account_currency as currency
from
`tabJournal Entry` t1, `tabJournal Entry Account` t2
where
@@ -88,7 +89,8 @@
if self.party_type == 'Customer' else "Purchase Invoice")
return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type,
- (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount
+ (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount,
+ account_currency as currency
FROM `tab{doc}`, `tabGL Entry`
WHERE
(`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no)
@@ -101,10 +103,10 @@
Having
amount > 0
""".format(
- doc=voucher_type,
- dr_or_cr=dr_or_cr,
- reconciled_dr_or_cr=reconciled_dr_or_cr,
- party_type_field=frappe.scrub(self.party_type)),
+ doc=voucher_type,
+ dr_or_cr=dr_or_cr,
+ reconciled_dr_or_cr=reconciled_dr_or_cr,
+ party_type_field=frappe.scrub(self.party_type)),
{
'party': self.party,
'party_type': self.party_type,
@@ -141,6 +143,7 @@
ent.invoice_number = e.get('voucher_no')
ent.invoice_date = e.get('posting_date')
ent.amount = flt(e.get('invoice_amount'))
+ ent.currency = e.get('currency')
ent.outstanding_amount = e.get('outstanding_amount')
def reconcile(self, args):
@@ -170,7 +173,7 @@
reconcile_against_document(lst)
if dr_or_cr_notes:
- reconcile_dr_cr_note(dr_or_cr_notes)
+ reconcile_dr_cr_note(dr_or_cr_notes, self.company)
msgprint(_("Successfully Reconciled"))
self.get_unreconciled_entries()
@@ -261,7 +264,7 @@
return cond
-def reconcile_dr_cr_note(dr_cr_notes):
+def reconcile_dr_cr_note(dr_cr_notes, company):
for d in dr_cr_notes:
voucher_type = ('Credit Note'
if d.voucher_type == 'Sales Invoice' else 'Debit Note')
@@ -269,10 +272,14 @@
reconcile_dr_or_cr = ('debit_in_account_currency'
if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
+ company_currency = erpnext.get_company_currency(company)
+
jv = frappe.get_doc({
"doctype": "Journal Entry",
"voucher_type": voucher_type,
"posting_date": today(),
+ "company": company,
+ "multi_currency": 1 if d.currency != company_currency else 0,
"accounts": [
{
'account': d.account,
@@ -280,7 +287,8 @@
'party_type': d.party_type,
d.dr_or_cr: abs(d.allocated_amount),
'reference_type': d.against_voucher_type,
- 'reference_name': d.against_voucher
+ 'reference_name': d.against_voucher,
+ 'cost_center': erpnext.get_default_cost_center(company)
},
{
'account': d.account,
@@ -289,7 +297,8 @@
reconcile_dr_or_cr: (abs(d.allocated_amount)
if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
'reference_type': d.voucher_type,
- 'reference_name': d.voucher_no
+ 'reference_name': d.voucher_no,
+ 'cost_center': erpnext.get_default_cost_center(company)
}
]
})
diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json
index ce7ce98..6a79a85 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json
@@ -1,183 +1,80 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2014-07-09 16:14:23.672922",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2014-07-09 16:14:23.672922",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "invoice_type",
+ "invoice_number",
+ "invoice_date",
+ "col_break1",
+ "amount",
+ "outstanding_amount",
+ "currency"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "invoice_type",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Invoice Type",
- "length": 0,
- "no_copy": 0,
- "options": "Sales Invoice\nPurchase Invoice\nJournal Entry",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "invoice_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Invoice Type",
+ "options": "Sales Invoice\nPurchase Invoice\nJournal Entry",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "invoice_number",
- "fieldtype": "Dynamic Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Invoice Number",
- "length": 0,
- "no_copy": 0,
- "options": "invoice_type",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "invoice_number",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Invoice Number",
+ "options": "invoice_type",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "invoice_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Invoice Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "invoice_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Invoice Date",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "col_break1",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "col_break1",
+ "fieldtype": "Column Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Amount",
+ "options": "currency",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "outstanding_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Outstanding Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "outstanding_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Outstanding Amount",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Currency",
+ "options": "Currency"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2016-07-11 03:28:03.588476",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Payment Reconciliation Invoice",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-07-19 18:12:27.964073",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Reconciliation Invoice",
+ "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/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
index 018bfd0..925a6f1 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
@@ -1,7 +1,9 @@
{
+ "actions": [],
"creation": "2014-07-09 16:13:35.452759",
"doctype": "DocType",
"editable_grid": 1,
+ "engine": "InnoDB",
"field_order": [
"reference_type",
"reference_name",
@@ -16,7 +18,8 @@
"difference_account",
"difference_amount",
"sec_break1",
- "remark"
+ "remark",
+ "currency"
],
"fields": [
{
@@ -73,6 +76,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
+ "options": "currency",
"read_only": 1
},
{
@@ -81,6 +85,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated amount",
+ "options": "currency",
"reqd": 1
},
{
@@ -106,16 +111,25 @@
"fieldname": "difference_amount",
"fieldtype": "Currency",
"label": "Difference Amount",
+ "options": "currency",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Currency",
+ "options": "Currency"
}
],
"istable": 1,
- "modified": "2019-06-24 00:08:11.150796",
+ "links": [],
+ "modified": "2020-07-19 18:12:41.682347",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Payment",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index eef6be1..8eadfd0 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -211,7 +211,7 @@
"label": "IBAN"
},
{
- "fetch_from": "bank.branch_code",
+ "fetch_from": "bank_account.branch_code",
"fetch_if_empty": 1,
"fieldname": "branch_code",
"fieldtype": "Read Only",
@@ -352,7 +352,7 @@
"in_create": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-29 17:38:49.392713",
+ "modified": "2020-07-17 14:06:42.185763",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 287e00f..e93ec95 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -140,9 +140,6 @@
})
def set_as_paid(self):
- if frappe.session.user == "Guest":
- frappe.set_user("Administrator")
-
payment_entry = self.create_payment_entry()
self.make_invoice()
@@ -254,7 +251,7 @@
if status in ["Authorized", "Completed"]:
redirect_to = None
- self.run_method("set_as_paid")
+ self.set_as_paid()
# if shopping cart enabled and in session
if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/pos_closing_entry/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/pos_closing_entry/__init__.py
diff --git a/erpnext/selling/doctype/pos_closing_voucher/closing_voucher_details.html b/erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html
similarity index 71%
rename from erpnext/selling/doctype/pos_closing_voucher/closing_voucher_details.html
rename to erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html
index 2412b07..983f495 100644
--- a/erpnext/selling/doctype/pos_closing_voucher/closing_voucher_details.html
+++ b/erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html
@@ -12,15 +12,15 @@
</thead>
<tbody>
<tr>
- <td class="text-left">{{ _('Grand Total') }}</td>
- <td class='text-right'>{{ data.grand_total or '' }} {{ currency.symbol }}</td>
+ <td class="text-left font-bold">{{ _('Grand Total') }}</td>
+ <td class='text-right'> {{ frappe.utils.fmt_money(data.grand_total or '', currency=currency) }}</td>
</tr>
<tr>
- <td class="text-left">{{ _('Net Total') }}</td>
- <td class='text-right'>{{ data.net_total or '' }} {{ currency.symbol }}</td>
+ <td class="text-left font-bold">{{ _('Net Total') }}</td>
+ <td class='text-right'> {{ frappe.utils.fmt_money(data.net_total or '', currency=currency) }}</td>
</tr>
<tr>
- <td class="text-left">{{ _('Total Quantity') }}</td>
+ <td class="text-left font-bold">{{ _('Total Quantity') }}</td>
<td class='text-right'>{{ data.total_quantity or '' }}</td>
</tr>
@@ -45,7 +45,7 @@
{% for d in data.payment_reconciliation %}
<tr>
<td class="text-left">{{ d.mode_of_payment }}</td>
- <td class='text-right'>{{ d.expected_amount }} {{ currency.symbol }}</td>
+ <td class='text-right'> {{ frappe.utils.fmt_money(d.expected_amount - d.opening_amount, currency=currency) }}</td>
</tr>
{% endfor %}
</tbody>
@@ -55,12 +55,14 @@
<!-- Section end -->
<!-- Taxes section -->
+ {% if data.taxes %}
<div>
<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Taxes") }}</h6>
<div class="tax-break-up" style="overflow-x: auto;">
<table class="table table-bordered table-hover">
<thead>
<tr>
+ <th class="text-left">{{ _("Account") }}</th>
<th class="text-left">{{ _("Rate") }}</th>
<th class="text-right">{{ _("Amount") }}</th>
</tr>
@@ -68,14 +70,16 @@
<tbody>
{% for d in data.taxes %}
<tr>
+ <td class="text-left">{{ d.account_head }}</td>
<td class="text-left">{{ d.rate }} %</td>
- <td class='text-right'>{{ d.amount }} {{ currency.symbol }}</td>
+ <td class='text-right'> {{ frappe.utils.fmt_money(d.amount, currency=currency) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
+ {% endif %}
<!-- Section end -->
</div>
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
new file mode 100644
index 0000000..8dcd2e4
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -0,0 +1,149 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('POS Closing Entry', {
+ onload: function(frm) {
+ frm.set_query("pos_profile", function(doc) {
+ return {
+ filters: { 'user': doc.user }
+ };
+ });
+
+ frm.set_query("user", function(doc) {
+ return {
+ query: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_cashiers",
+ filters: { 'parent': doc.pos_profile }
+ };
+ });
+
+ frm.set_query("pos_opening_entry", function(doc) {
+ return { filters: { 'status': 'Open', 'docstatus': 1 } };
+ });
+
+ if (frm.doc.docstatus === 0) frm.set_value("period_end_date", frappe.datetime.now_datetime());
+ if (frm.doc.docstatus === 1) set_html_data(frm);
+ },
+
+ pos_opening_entry(frm) {
+ if (frm.doc.pos_opening_entry && frm.doc.period_start_date && frm.doc.period_end_date && frm.doc.user) {
+ reset_values(frm);
+ frm.trigger("set_opening_amounts");
+ frm.trigger("get_pos_invoices");
+ }
+ },
+
+ set_opening_amounts(frm) {
+ frappe.db.get_doc("POS Opening Entry", frm.doc.pos_opening_entry)
+ .then(({ balance_details }) => {
+ balance_details.forEach(detail => {
+ frm.add_child("payment_reconciliation", {
+ mode_of_payment: detail.mode_of_payment,
+ opening_amount: detail.opening_amount,
+ expected_amount: detail.opening_amount
+ });
+ })
+ });
+ },
+
+ get_pos_invoices(frm) {
+ frappe.call({
+ method: 'erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices',
+ args: {
+ start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
+ end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
+ user: frm.doc.user
+ },
+ callback: (r) => {
+ let pos_docs = r.message;
+ set_form_data(pos_docs, frm)
+ refresh_fields(frm)
+ set_html_data(frm)
+ }
+ })
+ }
+});
+
+frappe.ui.form.on('POS Closing Entry Detail', {
+ closing_amount: (frm, cdt, cdn) => {
+ const row = locals[cdt][cdn];
+ frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount))
+ }
+})
+
+function set_form_data(data, frm) {
+ data.forEach(d => {
+ add_to_pos_transaction(d, frm);
+ frm.doc.grand_total += flt(d.grand_total);
+ frm.doc.net_total += flt(d.net_total);
+ frm.doc.total_quantity += flt(d.total_qty);
+ add_to_payments(d, frm);
+ add_to_taxes(d, frm);
+ });
+}
+
+function add_to_pos_transaction(d, frm) {
+ frm.add_child("pos_transactions", {
+ pos_invoice: d.name,
+ posting_date: d.posting_date,
+ grand_total: d.grand_total,
+ customer: d.customer
+ })
+}
+
+function add_to_payments(d, frm) {
+ d.payments.forEach(p => {
+ const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
+ if (payment) {
+ payment.expected_amount += flt(p.amount);
+ } else {
+ frm.add_child("payment_reconciliation", {
+ mode_of_payment: p.mode_of_payment,
+ opening_amount: 0,
+ expected_amount: p.amount
+ })
+ }
+ })
+}
+
+function add_to_taxes(d, frm) {
+ d.taxes.forEach(t => {
+ const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
+ if (tax) {
+ tax.amount += flt(t.tax_amount);
+ } else {
+ frm.add_child("taxes", {
+ account_head: t.account_head,
+ rate: t.rate,
+ amount: t.tax_amount
+ })
+ }
+ })
+}
+
+function reset_values(frm) {
+ frm.set_value("pos_transactions", []);
+ frm.set_value("payment_reconciliation", []);
+ frm.set_value("taxes", []);
+ frm.set_value("grand_total", 0);
+ frm.set_value("net_total", 0);
+ frm.set_value("total_quantity", 0);
+}
+
+function refresh_fields(frm) {
+ frm.refresh_field("pos_transactions");
+ frm.refresh_field("payment_reconciliation");
+ frm.refresh_field("taxes");
+ frm.refresh_field("grand_total");
+ frm.refresh_field("net_total");
+ frm.refresh_field("total_quantity");
+}
+
+function set_html_data(frm) {
+ frappe.call({
+ method: "get_payment_reconciliation_details",
+ doc: frm.doc,
+ callback: (r) => {
+ frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
+ }
+ })
+}
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
new file mode 100644
index 0000000..32bca3b
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
@@ -0,0 +1,242 @@
+{
+ "actions": [],
+ "autoname": "POS-CLO-.YYYY.-.#####",
+ "creation": "2018-05-28 19:06:40.830043",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "period_start_date",
+ "period_end_date",
+ "column_break_3",
+ "posting_date",
+ "pos_opening_entry",
+ "section_break_5",
+ "company",
+ "column_break_7",
+ "pos_profile",
+ "user",
+ "section_break_12",
+ "pos_transactions",
+ "section_break_9",
+ "payment_reconciliation_details",
+ "section_break_11",
+ "payment_reconciliation",
+ "section_break_13",
+ "grand_total",
+ "net_total",
+ "total_quantity",
+ "column_break_16",
+ "taxes",
+ "section_break_14",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fetch_from": "pos_opening_entry.period_start_date",
+ "fieldname": "period_start_date",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "Period Start Date",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "default": "Today",
+ "fieldname": "period_end_date",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "Period End Date",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "Today",
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Posting Date",
+ "reqd": 1
+ },
+ {
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "pos_opening_entry.pos_profile",
+ "fieldname": "pos_profile",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "POS Profile",
+ "options": "POS Profile",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "pos_opening_entry.user",
+ "fieldname": "user",
+ "fieldtype": "Link",
+ "label": "Cashier",
+ "options": "User",
+ "reqd": 1
+ },
+ {
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.docstatus==1",
+ "fieldname": "payment_reconciliation_details",
+ "fieldtype": "HTML"
+ },
+ {
+ "fieldname": "section_break_11",
+ "fieldtype": "Section Break",
+ "label": "Modes of Payment"
+ },
+ {
+ "fieldname": "payment_reconciliation",
+ "fieldtype": "Table",
+ "label": "Payment Reconciliation",
+ "options": "POS Closing Entry Detail"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:doc.docstatus==0",
+ "fieldname": "section_break_13",
+ "fieldtype": "Section Break",
+ "label": "Details"
+ },
+ {
+ "default": "0",
+ "fieldname": "grand_total",
+ "fieldtype": "Currency",
+ "label": "Grand Total",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "net_total",
+ "fieldtype": "Currency",
+ "label": "Net Total",
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_quantity",
+ "fieldtype": "Float",
+ "label": "Total Quantity",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_16",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "taxes",
+ "fieldtype": "Table",
+ "label": "Taxes",
+ "options": "POS Closing Entry Taxes",
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_12",
+ "fieldtype": "Section Break",
+ "label": "Linked Invoices"
+ },
+ {
+ "fieldname": "section_break_14",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "POS Closing Entry",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "pos_transactions",
+ "fieldtype": "Table",
+ "label": "POS Transactions",
+ "options": "POS Invoice Reference",
+ "reqd": 1
+ },
+ {
+ "fieldname": "pos_opening_entry",
+ "fieldtype": "Link",
+ "label": "POS Opening Entry",
+ "options": "POS Opening Entry",
+ "reqd": 1
+ }
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-05-29 15:03:22.226113",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Closing Entry",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "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
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Administrator",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
new file mode 100644
index 0000000..9899219
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import json
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import getdate, get_datetime, flt
+from collections import defaultdict
+from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
+from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
+
+class POSClosingEntry(Document):
+ def validate(self):
+ user = frappe.get_all('POS Closing Entry',
+ filters = { 'user': self.user, 'docstatus': 1 },
+ or_filters = {
+ 'period_start_date': ('between', [self.period_start_date, self.period_end_date]),
+ 'period_end_date': ('between', [self.period_start_date, self.period_end_date])
+ })
+
+ if user:
+ frappe.throw(_("POS Closing Entry {} against {} between selected period"
+ .format(frappe.bold("already exists"), frappe.bold(self.user))), title=_("Invalid Period"))
+
+ if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
+ frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
+
+ def on_submit(self):
+ merge_pos_invoices(self.pos_transactions)
+ opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
+ opening_entry.pos_closing_entry = self.name
+ opening_entry.set_status()
+ opening_entry.save()
+
+ def get_payment_reconciliation_details(self):
+ currency = frappe.get_cached_value('Company', self.company, "default_currency")
+ return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
+ {"data": self, "currency": currency})
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
+ cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'])
+ return [c['user'] for c in cashiers_list]
+
+@frappe.whitelist()
+def get_pos_invoices(start, end, user):
+ data = frappe.db.sql("""
+ select
+ name, timestamp(posting_date, posting_time) as "timestamp"
+ from
+ `tabPOS Invoice`
+ where
+ owner = %s and docstatus = 1 and
+ (consolidated_invoice is NULL or consolidated_invoice = '')
+ """, (user), as_dict=1)
+
+ data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
+ # need to get taxes and payments so can't avoid get_doc
+ data = [frappe.get_doc("POS Invoice", d.name).as_dict() for d in data]
+
+ return data
+
+def make_closing_entry_from_opening(opening_entry):
+ closing_entry = frappe.new_doc("POS Closing Entry")
+ closing_entry.pos_opening_entry = opening_entry.name
+ closing_entry.period_start_date = opening_entry.period_start_date
+ closing_entry.period_end_date = frappe.utils.get_datetime()
+ closing_entry.pos_profile = opening_entry.pos_profile
+ closing_entry.user = opening_entry.user
+ closing_entry.company = opening_entry.company
+ closing_entry.grand_total = 0
+ closing_entry.net_total = 0
+ closing_entry.total_quantity = 0
+
+ invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date, closing_entry.user)
+
+ pos_transactions = []
+ taxes = []
+ payments = []
+ for detail in opening_entry.balance_details:
+ payments.append(frappe._dict({
+ 'mode_of_payment': detail.mode_of_payment,
+ 'opening_amount': detail.opening_amount,
+ 'expected_amount': detail.opening_amount
+ }))
+
+ for d in invoices:
+ pos_transactions.append(frappe._dict({
+ 'pos_invoice': d.name,
+ 'posting_date': d.posting_date,
+ 'grand_total': d.grand_total,
+ 'customer': d.customer
+ }))
+ closing_entry.grand_total += flt(d.grand_total)
+ closing_entry.net_total += flt(d.net_total)
+ closing_entry.total_quantity += flt(d.total_qty)
+
+ for t in d.taxes:
+ existing_tax = [tx for tx in taxes if tx.account_head == t.account_head and tx.rate == t.rate]
+ if existing_tax:
+ existing_tax[0].amount += flt(t.tax_amount);
+ else:
+ taxes.append(frappe._dict({
+ 'account_head': t.account_head,
+ 'rate': t.rate,
+ 'amount': t.tax_amount
+ }))
+
+ for p in d.payments:
+ existing_pay = [pay for pay in payments if pay.mode_of_payment == p.mode_of_payment]
+ if existing_pay:
+ existing_pay[0].expected_amount += flt(p.amount);
+ else:
+ payments.append(frappe._dict({
+ 'mode_of_payment': p.mode_of_payment,
+ 'opening_amount': 0,
+ 'expected_amount': p.amount
+ }))
+
+ closing_entry.set("pos_transactions", pos_transactions)
+ closing_entry.set("payment_reconciliation", payments)
+ closing_entry.set("taxes", taxes)
+
+ return closing_entry
diff --git a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js
similarity index 68%
copy from erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js
copy to erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js
index 7633815..48109b1 100644
--- a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js
@@ -2,15 +2,15 @@
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
-QUnit.test("test: POS Closing Voucher", function (assert) {
+QUnit.test("test: POS Closing Entry", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
- // insert a new POS Closing Voucher
- () => frappe.tests.make('POS Closing Voucher', [
+ // insert a new POS Closing Entry
+ () => frappe.tests.make('POS Closing Entry', [
// values to be set
{key: 'value'}
]),
diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
new file mode 100644
index 0000000..aa6a388
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+import frappe
+import unittest
+from frappe.utils import nowdate
+from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
+from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening
+from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
+
+class TestPOSClosingEntry(unittest.TestCase):
+ def test_pos_closing_entry(self):
+ test_user, pos_profile = init_user_and_profile()
+
+ opening_entry = create_opening_entry(pos_profile, test_user.name)
+
+ pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
+ pos_inv1.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500
+ })
+ pos_inv1.submit()
+
+ pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
+ pos_inv2.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
+ })
+ pos_inv2.submit()
+
+ pcv_doc = make_closing_entry_from_opening(opening_entry)
+ payment = pcv_doc.payment_reconciliation[0]
+
+ self.assertEqual(payment.mode_of_payment, 'Cash')
+
+ for d in pcv_doc.payment_reconciliation:
+ if d.mode_of_payment == 'Cash':
+ d.closing_amount = 6700
+
+ pcv_doc.submit()
+
+ self.assertEqual(pcv_doc.total_quantity, 2)
+ self.assertEqual(pcv_doc.net_total, 6700)
+
+ frappe.set_user("Administrator")
+ frappe.db.sql("delete from `tabPOS Profile`")
+
+def init_user_and_profile():
+ user = 'test@example.com'
+ test_user = frappe.get_doc('User', user)
+
+ roles = ("Accounts Manager", "Accounts User", "Sales Manager")
+ test_user.add_roles(*roles)
+ frappe.set_user(user)
+
+ pos_profile = make_pos_profile()
+ pos_profile.append('applicable_for_users', {
+ 'default': 1,
+ 'user': user
+ })
+
+ pos_profile.save()
+
+ return test_user, pos_profile
\ No newline at end of file
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/pos_closing_entry_detail/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/pos_closing_entry_detail/__init__.py
diff --git a/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json
new file mode 100644
index 0000000..798637a
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json
@@ -0,0 +1,70 @@
+{
+ "actions": [],
+ "creation": "2018-05-28 19:10:47.580174",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "mode_of_payment",
+ "opening_amount",
+ "closing_amount",
+ "expected_amount",
+ "difference"
+ ],
+ "fields": [
+ {
+ "fieldname": "mode_of_payment",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Mode of Payment",
+ "options": "Mode of Payment",
+ "reqd": 1
+ },
+ {
+ "fieldname": "expected_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Expected Amount",
+ "options": "company:company_currency",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "difference",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Difference",
+ "options": "company:company_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "opening_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Opening Amount",
+ "options": "company:company_currency",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "closing_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Closing Amount",
+ "options": "company:company_currency",
+ "reqd": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-05-29 15:03:34.533607",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Closing Entry Detail",
+ "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/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.py
similarity index 84%
copy from erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py
copy to erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.py
index 87ce842..46b6c77 100644
--- a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py
+++ b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.py
@@ -5,5 +5,5 @@
from __future__ import unicode_literals
from frappe.model.document import Document
-class POSClosingVoucherTaxes(Document):
+class POSClosingEntryDetail(Document):
pass
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/pos_closing_entry_taxes/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/pos_closing_entry_taxes/__init__.py
diff --git a/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json b/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json
new file mode 100644
index 0000000..42e7d0e
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json
@@ -0,0 +1,48 @@
+{
+ "actions": [],
+ "creation": "2018-05-30 09:11:22.535470",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "account_head",
+ "rate",
+ "amount"
+ ],
+ "fields": [
+ {
+ "fieldname": "rate",
+ "fieldtype": "Percent",
+ "in_list_view": 1,
+ "label": "Rate",
+ "read_only": 1
+ },
+ {
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Amount",
+ "read_only": 1
+ },
+ {
+ "fieldname": "account_head",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account Head",
+ "options": "Account",
+ "read_only": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-05-29 15:03:39.872884",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Closing Entry Taxes",
+ "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/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py b/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.py
similarity index 84%
rename from erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py
rename to erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.py
index 87ce842..f72d9a6 100644
--- a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py
+++ b/erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.py
@@ -5,5 +5,5 @@
from __future__ import unicode_literals
from frappe.model.document import Document
-class POSClosingVoucherTaxes(Document):
+class POSClosingEntryTaxes(Document):
pass
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/pos_invoice/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/pos_invoice/__init__.py
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
new file mode 100644
index 0000000..3be4304
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -0,0 +1,205 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+{% include 'erpnext/selling/sales_common.js' %};
+
+erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({
+ setup(doc) {
+ this.setup_posting_date_time_check();
+ this._super(doc);
+ },
+
+ onload() {
+ this._super();
+ if(this.frm.doc.__islocal && this.frm.doc.is_pos) {
+ //Load pos profile data on the invoice if the default value of Is POS is 1
+
+ me.frm.script_manager.trigger("is_pos");
+ me.frm.refresh_fields();
+ }
+ },
+
+ refresh(doc) {
+ this._super();
+ if (doc.docstatus == 1 && !doc.is_return) {
+ if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) {
+ cur_frm.add_custom_button(__('Return'),
+ this.make_sales_return, __('Create'));
+ cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
+ }
+ }
+
+ if (this.frm.doc.is_return) {
+ this.frm.return_print_format = "Sales Invoice Return";
+ cur_frm.set_value('consolidated_invoice', '');
+ }
+ },
+
+ is_pos: function(frm){
+ this.set_pos_data();
+ },
+
+ set_pos_data: function() {
+ if(this.frm.doc.is_pos) {
+ this.frm.set_value("allocate_advances_automatically", 0);
+ if(!this.frm.doc.company) {
+ this.frm.set_value("is_pos", 0);
+ frappe.msgprint(__("Please specify Company to proceed"));
+ } else {
+ var me = this;
+ return this.frm.call({
+ doc: me.frm.doc,
+ method: "set_missing_values",
+ callback: function(r) {
+ if(!r.exc) {
+ if(r.message) {
+ me.frm.pos_print_format = r.message.print_format || "";
+ me.frm.meta.default_print_format = r.message.print_format || "";
+ me.frm.allow_edit_rate = r.message.allow_edit_rate;
+ me.frm.allow_edit_discount = r.message.allow_edit_discount;
+ me.frm.doc.campaign = r.message.campaign;
+ me.frm.allow_print_before_pay = r.message.allow_print_before_pay;
+ }
+ me.frm.script_manager.trigger("update_stock");
+ me.calculate_taxes_and_totals();
+ if(me.frm.doc.taxes_and_charges) {
+ me.frm.script_manager.trigger("taxes_and_charges");
+ }
+ frappe.model.set_default_values(me.frm.doc);
+ me.set_dynamic_labels();
+
+ }
+ }
+ });
+ }
+ }
+ else this.frm.trigger("refresh");
+ },
+
+ customer() {
+ if (!this.frm.doc.customer) return
+
+ if (this.frm.doc.is_pos){
+ var pos_profile = this.frm.doc.pos_profile;
+ }
+ var me = this;
+ if(this.frm.updating_party_details) return;
+ erpnext.utils.get_party_details(this.frm,
+ "erpnext.accounts.party.get_party_details", {
+ posting_date: this.frm.doc.posting_date,
+ party: this.frm.doc.customer,
+ party_type: "Customer",
+ account: this.frm.doc.debit_to,
+ price_list: this.frm.doc.selling_price_list,
+ pos_profile: pos_profile
+ }, function() {
+ me.apply_pricing_rule();
+ });
+ },
+
+ amount: function(){
+ this.write_off_outstanding_amount_automatically()
+ },
+
+ change_amount: function(){
+ if(this.frm.doc.paid_amount > this.frm.doc.grand_total){
+ this.calculate_write_off_amount();
+ }else {
+ this.frm.set_value("change_amount", 0.0);
+ this.frm.set_value("base_change_amount", 0.0);
+ }
+
+ this.frm.refresh_fields();
+ },
+
+ loyalty_amount: function(){
+ this.calculate_outstanding_amount();
+ this.frm.refresh_field("outstanding_amount");
+ this.frm.refresh_field("paid_amount");
+ this.frm.refresh_field("base_paid_amount");
+ },
+
+ write_off_outstanding_amount_automatically: function() {
+ if(cint(this.frm.doc.write_off_outstanding_amount_automatically)) {
+ frappe.model.round_floats_in(this.frm.doc, ["grand_total", "paid_amount"]);
+ // this will make outstanding amount 0
+ this.frm.set_value("write_off_amount",
+ flt(this.frm.doc.grand_total - this.frm.doc.paid_amount - this.frm.doc.total_advance, precision("write_off_amount"))
+ );
+ this.frm.toggle_enable("write_off_amount", false);
+
+ } else {
+ this.frm.toggle_enable("write_off_amount", true);
+ }
+
+ this.calculate_outstanding_amount(false);
+ this.frm.refresh_fields();
+ },
+
+ make_sales_return: function() {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return",
+ frm: cur_frm
+ })
+ },
+})
+
+$.extend(cur_frm.cscript, new erpnext.selling.POSInvoiceController({ frm: cur_frm }))
+
+frappe.ui.form.on('POS Invoice', {
+ redeem_loyalty_points: function(frm) {
+ frm.events.get_loyalty_details(frm);
+ },
+
+ loyalty_points: function(frm) {
+ if (frm.redemption_conversion_factor) {
+ frm.events.set_loyalty_points(frm);
+ } else {
+ frappe.call({
+ method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_redeemption_factor",
+ args: {
+ "loyalty_program": frm.doc.loyalty_program
+ },
+ callback: function(r) {
+ if (r) {
+ frm.redemption_conversion_factor = r.message;
+ frm.events.set_loyalty_points(frm);
+ }
+ }
+ });
+ }
+ },
+
+ get_loyalty_details: function(frm) {
+ if (frm.doc.customer && frm.doc.redeem_loyalty_points) {
+ frappe.call({
+ method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details",
+ args: {
+ "customer": frm.doc.customer,
+ "loyalty_program": frm.doc.loyalty_program,
+ "expiry_date": frm.doc.posting_date,
+ "company": frm.doc.company
+ },
+ callback: function(r) {
+ if (r) {
+ frm.set_value("loyalty_redemption_account", r.message.expense_account);
+ frm.set_value("loyalty_redemption_cost_center", r.message.cost_center);
+ frm.redemption_conversion_factor = r.message.conversion_factor;
+ }
+ }
+ });
+ }
+ },
+
+ set_loyalty_points: function(frm) {
+ if (frm.redemption_conversion_factor) {
+ let loyalty_amount = flt(frm.redemption_conversion_factor*flt(frm.doc.loyalty_points), precision("loyalty_amount"));
+ var remaining_amount = flt(frm.doc.grand_total) - flt(frm.doc.total_advance) - flt(frm.doc.write_off_amount);
+ if (frm.doc.grand_total && (remaining_amount < loyalty_amount)) {
+ let redeemable_points = parseInt(remaining_amount/frm.redemption_conversion_factor);
+ frappe.throw(__("You can only redeem max {0} points in this order.",[redeemable_points]));
+ }
+ frm.set_value("loyalty_amount", loyalty_amount);
+ }
+ }
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
new file mode 100644
index 0000000..2a2e3df
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -0,0 +1,1637 @@
+{
+ "actions": [],
+ "allow_import": 1,
+ "autoname": "naming_series:",
+ "creation": "2020-01-24 15:29:29.933693",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "customer_section",
+ "title",
+ "naming_series",
+ "customer",
+ "customer_name",
+ "tax_id",
+ "is_pos",
+ "pos_profile",
+ "offline_pos_name",
+ "is_return",
+ "consolidated_invoice",
+ "column_break1",
+ "company",
+ "posting_date",
+ "posting_time",
+ "set_posting_time",
+ "due_date",
+ "amended_from",
+ "returns",
+ "return_against",
+ "column_break_21",
+ "update_billed_amount_in_sales_order",
+ "accounting_dimensions_section",
+ "project",
+ "dimension_col_break",
+ "cost_center",
+ "customer_po_details",
+ "po_no",
+ "column_break_23",
+ "po_date",
+ "address_and_contact",
+ "customer_address",
+ "address_display",
+ "contact_person",
+ "contact_display",
+ "contact_mobile",
+ "contact_email",
+ "territory",
+ "col_break4",
+ "shipping_address_name",
+ "shipping_address",
+ "company_address",
+ "company_address_display",
+ "currency_and_price_list",
+ "currency",
+ "conversion_rate",
+ "column_break2",
+ "selling_price_list",
+ "price_list_currency",
+ "plc_conversion_rate",
+ "ignore_pricing_rule",
+ "sec_warehouse",
+ "set_warehouse",
+ "items_section",
+ "update_stock",
+ "scan_barcode",
+ "items",
+ "pricing_rule_details",
+ "pricing_rules",
+ "packing_list",
+ "packed_items",
+ "product_bundle_help",
+ "time_sheet_list",
+ "timesheets",
+ "total_billing_amount",
+ "section_break_30",
+ "total_qty",
+ "base_total",
+ "base_net_total",
+ "column_break_32",
+ "total",
+ "net_total",
+ "total_net_weight",
+ "taxes_section",
+ "taxes_and_charges",
+ "column_break_38",
+ "shipping_rule",
+ "tax_category",
+ "section_break_40",
+ "taxes",
+ "sec_tax_breakup",
+ "other_charges_calculation",
+ "section_break_43",
+ "base_total_taxes_and_charges",
+ "column_break_47",
+ "total_taxes_and_charges",
+ "loyalty_points_redemption",
+ "loyalty_points",
+ "loyalty_amount",
+ "redeem_loyalty_points",
+ "column_break_77",
+ "loyalty_program",
+ "loyalty_redemption_account",
+ "loyalty_redemption_cost_center",
+ "section_break_49",
+ "apply_discount_on",
+ "base_discount_amount",
+ "column_break_51",
+ "additional_discount_percentage",
+ "discount_amount",
+ "totals",
+ "base_grand_total",
+ "base_rounding_adjustment",
+ "base_rounded_total",
+ "base_in_words",
+ "column_break5",
+ "grand_total",
+ "rounding_adjustment",
+ "rounded_total",
+ "in_words",
+ "total_advance",
+ "outstanding_amount",
+ "advances_section",
+ "allocate_advances_automatically",
+ "get_advances",
+ "advances",
+ "payment_schedule_section",
+ "payment_terms_template",
+ "payment_schedule",
+ "payments_section",
+ "cash_bank_account",
+ "payments",
+ "section_break_84",
+ "base_paid_amount",
+ "column_break_86",
+ "paid_amount",
+ "section_break_88",
+ "base_change_amount",
+ "column_break_90",
+ "change_amount",
+ "account_for_change_amount",
+ "column_break4",
+ "write_off_amount",
+ "base_write_off_amount",
+ "write_off_outstanding_amount_automatically",
+ "column_break_74",
+ "write_off_account",
+ "write_off_cost_center",
+ "terms_section_break",
+ "tc_name",
+ "terms",
+ "edit_printing_settings",
+ "letter_head",
+ "group_same_items",
+ "language",
+ "column_break_84",
+ "select_print_heading",
+ "more_information",
+ "inter_company_invoice_reference",
+ "customer_group",
+ "campaign",
+ "is_discounted",
+ "col_break23",
+ "status",
+ "source",
+ "more_info",
+ "debit_to",
+ "party_account_currency",
+ "is_opening",
+ "c_form_applicable",
+ "c_form_no",
+ "column_break8",
+ "remarks",
+ "sales_team_section_break",
+ "sales_partner",
+ "column_break10",
+ "commission_rate",
+ "total_commission",
+ "section_break2",
+ "sales_team",
+ "subscription_section",
+ "from_date",
+ "to_date",
+ "column_break_140",
+ "auto_repeat",
+ "update_auto_repeat_reference",
+ "against_income_account",
+ "pos_total_qty"
+ ],
+ "fields": [
+ {
+ "fieldname": "customer_section",
+ "fieldtype": "Section Break",
+ "options": "fa fa-user"
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "{customer_name}",
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Title",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "bold": 1,
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "no_copy": 1,
+ "oldfieldname": "naming_series",
+ "oldfieldtype": "Select",
+ "options": "ACC-PSINV-.YYYY.-",
+ "print_hide": 1,
+ "reqd": 1,
+ "set_only_once": 1
+ },
+ {
+ "bold": 1,
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Customer",
+ "oldfieldname": "customer",
+ "oldfieldtype": "Link",
+ "options": "Customer",
+ "print_hide": 1,
+ "search_index": 1
+ },
+ {
+ "bold": 1,
+ "depends_on": "customer",
+ "fetch_from": "customer.customer_name",
+ "fieldname": "customer_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "label": "Customer Name",
+ "oldfieldname": "customer_name",
+ "oldfieldtype": "Data",
+ "read_only": 1
+ },
+ {
+ "fieldname": "tax_id",
+ "fieldtype": "Data",
+ "label": "Tax Id",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "is_pos",
+ "fieldtype": "Check",
+ "label": "Include Payment (POS)",
+ "oldfieldname": "is_pos",
+ "oldfieldtype": "Check",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "depends_on": "is_pos",
+ "fieldname": "pos_profile",
+ "fieldtype": "Link",
+ "label": "POS Profile",
+ "options": "POS Profile",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "offline_pos_name",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Offline POS Name",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "is_return",
+ "fieldtype": "Check",
+ "label": "Is Return (Credit Note)",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "column_break1",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Company",
+ "oldfieldname": "company",
+ "oldfieldtype": "Link",
+ "options": "Company",
+ "print_hide": 1,
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
+ {
+ "bold": 1,
+ "default": "Today",
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "label": "Date",
+ "no_copy": 1,
+ "oldfieldname": "posting_date",
+ "oldfieldtype": "Date",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "posting_time",
+ "fieldtype": "Time",
+ "label": "Posting Time",
+ "no_copy": 1,
+ "oldfieldname": "posting_time",
+ "oldfieldtype": "Time",
+ "print_hide": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.docstatus==0",
+ "fieldname": "set_posting_time",
+ "fieldtype": "Check",
+ "label": "Edit Posting Date and Time",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "due_date",
+ "fieldtype": "Date",
+ "label": "Payment Due Date",
+ "no_copy": 1,
+ "oldfieldname": "due_date",
+ "oldfieldtype": "Date"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "label": "Amended From",
+ "no_copy": 1,
+ "oldfieldname": "amended_from",
+ "oldfieldtype": "Link",
+ "options": "POS Invoice",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "depends_on": "return_against",
+ "fieldname": "returns",
+ "fieldtype": "Section Break",
+ "label": "Returns"
+ },
+ {
+ "depends_on": "return_against",
+ "fieldname": "return_against",
+ "fieldtype": "Link",
+ "label": "Return Against POS Invoice",
+ "no_copy": 1,
+ "options": "POS Invoice",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_21",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval: doc.is_return && doc.return_against",
+ "fieldname": "update_billed_amount_in_sales_order",
+ "fieldtype": "Check",
+ "label": "Update Billed Amount in Sales Order"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "label": "Project",
+ "oldfieldname": "project_name",
+ "oldfieldtype": "Link",
+ "options": "Project",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "po_no",
+ "fieldname": "customer_po_details",
+ "fieldtype": "Section Break",
+ "label": "Customer PO Details"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "po_no",
+ "fieldtype": "Data",
+ "label": "Customer's Purchase Order",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "column_break_23",
+ "fieldtype": "Column Break"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "po_date",
+ "fieldtype": "Date",
+ "label": "Customer's Purchase Order Date"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "address_and_contact",
+ "fieldtype": "Section Break",
+ "label": "Address and Contact"
+ },
+ {
+ "fieldname": "customer_address",
+ "fieldtype": "Link",
+ "label": "Customer Address",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "address_display",
+ "fieldtype": "Small Text",
+ "label": "Address",
+ "read_only": 1
+ },
+ {
+ "fieldname": "contact_person",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "label": "Contact Person",
+ "options": "Contact",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "contact_display",
+ "fieldtype": "Small Text",
+ "label": "Contact",
+ "read_only": 1
+ },
+ {
+ "fieldname": "contact_mobile",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Mobile No",
+ "read_only": 1
+ },
+ {
+ "fieldname": "contact_email",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Contact Email",
+ "options": "Email",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "label": "Territory",
+ "options": "Territory",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "col_break4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "shipping_address_name",
+ "fieldtype": "Link",
+ "label": "Shipping Address Name",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "shipping_address",
+ "fieldtype": "Small Text",
+ "label": "Shipping Address",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "company_address",
+ "fieldtype": "Link",
+ "label": "Company Address Name",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "company_address_display",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Company Address",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "depends_on": "customer",
+ "fieldname": "currency_and_price_list",
+ "fieldtype": "Section Break",
+ "label": "Currency and Price List"
+ },
+ {
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "oldfieldname": "currency",
+ "oldfieldtype": "Select",
+ "options": "Currency",
+ "print_hide": 1,
+ "reqd": 1
+ },
+ {
+ "description": "Rate at which Customer Currency is converted to customer's base currency",
+ "fieldname": "conversion_rate",
+ "fieldtype": "Float",
+ "label": "Exchange Rate",
+ "oldfieldname": "conversion_rate",
+ "oldfieldtype": "Currency",
+ "precision": "9",
+ "print_hide": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break2",
+ "fieldtype": "Column Break",
+ "width": "50%"
+ },
+ {
+ "fieldname": "selling_price_list",
+ "fieldtype": "Link",
+ "label": "Price List",
+ "oldfieldname": "price_list_name",
+ "oldfieldtype": "Select",
+ "options": "Price List",
+ "print_hide": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "price_list_currency",
+ "fieldtype": "Link",
+ "label": "Price List Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "description": "Rate at which Price list currency is converted to customer's base currency",
+ "fieldname": "plc_conversion_rate",
+ "fieldtype": "Float",
+ "label": "Price List Exchange Rate",
+ "precision": "9",
+ "print_hide": 1,
+ "reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "ignore_pricing_rule",
+ "fieldtype": "Check",
+ "label": "Ignore Pricing Rule",
+ "no_copy": 1,
+ "permlevel": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "sec_warehouse",
+ "fieldtype": "Section Break"
+ },
+ {
+ "depends_on": "update_stock",
+ "fieldname": "set_warehouse",
+ "fieldtype": "Link",
+ "label": "Set Source Warehouse",
+ "options": "Warehouse",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "items_section",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-shopping-cart"
+ },
+ {
+ "default": "0",
+ "fieldname": "update_stock",
+ "fieldtype": "Check",
+ "label": "Update Stock",
+ "oldfieldname": "update_stock",
+ "oldfieldtype": "Check",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "scan_barcode",
+ "fieldtype": "Data",
+ "label": "Scan Barcode"
+ },
+ {
+ "allow_bulk_edit": 1,
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "label": "Items",
+ "oldfieldname": "entries",
+ "oldfieldtype": "Table",
+ "options": "POS Invoice Item",
+ "reqd": 1
+ },
+ {
+ "fieldname": "pricing_rule_details",
+ "fieldtype": "Section Break",
+ "label": "Pricing Rules"
+ },
+ {
+ "fieldname": "pricing_rules",
+ "fieldtype": "Table",
+ "label": "Pricing Rule Detail",
+ "options": "Pricing Rule Detail",
+ "read_only": 1
+ },
+ {
+ "fieldname": "packing_list",
+ "fieldtype": "Section Break",
+ "label": "Packing List",
+ "options": "fa fa-suitcase",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "packed_items",
+ "fieldtype": "Table",
+ "label": "Packed Items",
+ "options": "Packed Item",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "product_bundle_help",
+ "fieldtype": "HTML",
+ "label": "Product Bundle Help",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:doc.total_billing_amount > 0",
+ "fieldname": "time_sheet_list",
+ "fieldtype": "Section Break",
+ "label": "Time Sheet List"
+ },
+ {
+ "fieldname": "timesheets",
+ "fieldtype": "Table",
+ "label": "Time Sheets",
+ "options": "Sales Invoice Timesheet",
+ "print_hide": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "total_billing_amount",
+ "fieldtype": "Currency",
+ "label": "Total Billing Amount",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_30",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "total_qty",
+ "fieldtype": "Float",
+ "label": "Total Quantity",
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_total",
+ "fieldtype": "Currency",
+ "label": "Total (Company Currency)",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_net_total",
+ "fieldtype": "Currency",
+ "label": "Net Total (Company Currency)",
+ "oldfieldname": "net_total",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_32",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "total",
+ "fieldtype": "Currency",
+ "label": "Total",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "net_total",
+ "fieldtype": "Currency",
+ "label": "Net Total",
+ "options": "currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_net_weight",
+ "fieldtype": "Float",
+ "label": "Total Net Weight",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "taxes_section",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-money"
+ },
+ {
+ "fieldname": "taxes_and_charges",
+ "fieldtype": "Link",
+ "label": "Sales Taxes and Charges Template",
+ "oldfieldname": "charge",
+ "oldfieldtype": "Link",
+ "options": "Sales Taxes and Charges Template",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "column_break_38",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "shipping_rule",
+ "fieldtype": "Link",
+ "label": "Shipping Rule",
+ "oldfieldtype": "Button",
+ "options": "Shipping Rule",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "tax_category",
+ "fieldtype": "Link",
+ "label": "Tax Category",
+ "options": "Tax Category",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "section_break_40",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "taxes",
+ "fieldtype": "Table",
+ "label": "Sales Taxes and Charges",
+ "oldfieldname": "other_charges",
+ "oldfieldtype": "Table",
+ "options": "Sales Taxes and Charges"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "sec_tax_breakup",
+ "fieldtype": "Section Break",
+ "label": "Tax Breakup"
+ },
+ {
+ "fieldname": "other_charges_calculation",
+ "fieldtype": "Long Text",
+ "label": "Taxes and Charges Calculation",
+ "no_copy": 1,
+ "oldfieldtype": "HTML",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_43",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "base_total_taxes_and_charges",
+ "fieldtype": "Currency",
+ "label": "Total Taxes and Charges (Company Currency)",
+ "oldfieldname": "other_charges_total",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_47",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "total_taxes_and_charges",
+ "fieldtype": "Currency",
+ "label": "Total Taxes and Charges",
+ "options": "currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "loyalty_points_redemption",
+ "fieldtype": "Section Break",
+ "label": "Loyalty Points Redemption"
+ },
+ {
+ "depends_on": "redeem_loyalty_points",
+ "fieldname": "loyalty_points",
+ "fieldtype": "Int",
+ "label": "Loyalty Points",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "depends_on": "redeem_loyalty_points",
+ "fieldname": "loyalty_amount",
+ "fieldtype": "Currency",
+ "label": "Loyalty Amount",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "redeem_loyalty_points",
+ "fieldtype": "Check",
+ "label": "Redeem Loyalty Points",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "column_break_77",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "customer.loyalty_program",
+ "fieldname": "loyalty_program",
+ "fieldtype": "Link",
+ "label": "Loyalty Program",
+ "no_copy": 1,
+ "options": "Loyalty Program",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "depends_on": "redeem_loyalty_points",
+ "fieldname": "loyalty_redemption_account",
+ "fieldtype": "Link",
+ "label": "Redemption Account",
+ "no_copy": 1,
+ "options": "Account"
+ },
+ {
+ "depends_on": "redeem_loyalty_points",
+ "fieldname": "loyalty_redemption_cost_center",
+ "fieldtype": "Link",
+ "label": "Redemption Cost Center",
+ "no_copy": 1,
+ "options": "Cost Center"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "discount_amount",
+ "fieldname": "section_break_49",
+ "fieldtype": "Section Break",
+ "label": "Additional Discount"
+ },
+ {
+ "default": "Grand Total",
+ "fieldname": "apply_discount_on",
+ "fieldtype": "Select",
+ "label": "Apply Additional Discount On",
+ "options": "\nGrand Total\nNet Total",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "base_discount_amount",
+ "fieldtype": "Currency",
+ "label": "Additional Discount Amount (Company Currency)",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_51",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "additional_discount_percentage",
+ "fieldtype": "Float",
+ "label": "Additional Discount Percentage",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "discount_amount",
+ "fieldtype": "Currency",
+ "label": "Additional Discount Amount",
+ "options": "currency",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "totals",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-money",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "base_grand_total",
+ "fieldtype": "Currency",
+ "label": "Grand Total (Company Currency)",
+ "oldfieldname": "grand_total",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "base_rounding_adjustment",
+ "fieldtype": "Currency",
+ "label": "Rounding Adjustment (Company Currency)",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_rounded_total",
+ "fieldtype": "Currency",
+ "label": "Rounded Total (Company Currency)",
+ "oldfieldname": "rounded_total",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "description": "In Words will be visible once you save the Sales Invoice.",
+ "fieldname": "base_in_words",
+ "fieldtype": "Data",
+ "label": "In Words (Company Currency)",
+ "oldfieldname": "in_words",
+ "oldfieldtype": "Data",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break5",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break",
+ "print_hide": 1,
+ "width": "50%"
+ },
+ {
+ "bold": 1,
+ "fieldname": "grand_total",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Grand Total",
+ "oldfieldname": "grand_total_export",
+ "oldfieldtype": "Currency",
+ "options": "currency",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "rounding_adjustment",
+ "fieldtype": "Currency",
+ "label": "Rounding Adjustment",
+ "no_copy": 1,
+ "options": "currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "bold": 1,
+ "fieldname": "rounded_total",
+ "fieldtype": "Currency",
+ "label": "Rounded Total",
+ "oldfieldname": "rounded_total_export",
+ "oldfieldtype": "Currency",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "in_words",
+ "fieldtype": "Data",
+ "label": "In Words",
+ "oldfieldname": "in_words_export",
+ "oldfieldtype": "Data",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_advance",
+ "fieldtype": "Currency",
+ "label": "Total Advance",
+ "oldfieldname": "total_advance",
+ "oldfieldtype": "Currency",
+ "options": "party_account_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "outstanding_amount",
+ "fieldtype": "Currency",
+ "label": "Outstanding Amount",
+ "no_copy": 1,
+ "oldfieldname": "outstanding_amount",
+ "oldfieldtype": "Currency",
+ "options": "party_account_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "advances",
+ "fieldname": "advances_section",
+ "fieldtype": "Section Break",
+ "label": "Advance Payments",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-money",
+ "print_hide": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "allocate_advances_automatically",
+ "fieldtype": "Check",
+ "label": "Allocate Advances Automatically (FIFO)"
+ },
+ {
+ "depends_on": "eval:!doc.allocate_advances_automatically",
+ "fieldname": "get_advances",
+ "fieldtype": "Button",
+ "label": "Get Advances Received",
+ "options": "set_advances"
+ },
+ {
+ "fieldname": "advances",
+ "fieldtype": "Table",
+ "label": "Advances",
+ "oldfieldname": "advance_adjustment_details",
+ "oldfieldtype": "Table",
+ "options": "Sales Invoice Advance",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:(!doc.is_pos && !doc.is_return)",
+ "fieldname": "payment_schedule_section",
+ "fieldtype": "Section Break",
+ "label": "Payment Terms"
+ },
+ {
+ "depends_on": "eval:(!doc.is_pos && !doc.is_return)",
+ "fieldname": "payment_terms_template",
+ "fieldtype": "Link",
+ "label": "Payment Terms Template",
+ "no_copy": 1,
+ "options": "Payment Terms Template",
+ "print_hide": 1
+ },
+ {
+ "depends_on": "eval:(!doc.is_pos && !doc.is_return)",
+ "fieldname": "payment_schedule",
+ "fieldtype": "Table",
+ "label": "Payment Schedule",
+ "no_copy": 1,
+ "options": "Payment Schedule",
+ "print_hide": 1
+ },
+ {
+ "depends_on": "eval:doc.is_pos===1||(doc.advances && doc.advances.length>0)",
+ "fieldname": "payments_section",
+ "fieldtype": "Section Break",
+ "label": "Payments",
+ "options": "fa fa-money"
+ },
+ {
+ "depends_on": "is_pos",
+ "fieldname": "cash_bank_account",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Cash/Bank Account",
+ "oldfieldname": "cash_bank_account",
+ "oldfieldtype": "Link",
+ "options": "Account",
+ "print_hide": 1
+ },
+ {
+ "depends_on": "eval:doc.is_pos===1",
+ "fieldname": "payments",
+ "fieldtype": "Table",
+ "label": "Sales Invoice Payment",
+ "options": "Sales Invoice Payment",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "section_break_84",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "base_paid_amount",
+ "fieldtype": "Currency",
+ "label": "Paid Amount (Company Currency)",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_86",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "eval: doc.is_pos || doc.redeem_loyalty_points",
+ "fieldname": "paid_amount",
+ "fieldtype": "Currency",
+ "label": "Paid Amount",
+ "no_copy": 1,
+ "oldfieldname": "paid_amount",
+ "oldfieldtype": "Currency",
+ "options": "currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_88",
+ "fieldtype": "Section Break"
+ },
+ {
+ "depends_on": "is_pos",
+ "fieldname": "base_change_amount",
+ "fieldtype": "Currency",
+ "label": "Base Change Amount (Company Currency)",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_90",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "is_pos",
+ "fieldname": "change_amount",
+ "fieldtype": "Currency",
+ "label": "Change Amount",
+ "no_copy": 1,
+ "options": "currency",
+ "print_hide": 1
+ },
+ {
+ "depends_on": "is_pos",
+ "fieldname": "account_for_change_amount",
+ "fieldtype": "Link",
+ "label": "Account for Change Amount",
+ "options": "Account",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "write_off_amount",
+ "depends_on": "grand_total",
+ "fieldname": "column_break4",
+ "fieldtype": "Section Break",
+ "label": "Write Off",
+ "width": "50%"
+ },
+ {
+ "fieldname": "write_off_amount",
+ "fieldtype": "Currency",
+ "label": "Write Off Amount",
+ "no_copy": 1,
+ "options": "currency",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "base_write_off_amount",
+ "fieldtype": "Currency",
+ "label": "Write Off Amount (Company Currency)",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "is_pos",
+ "fieldname": "write_off_outstanding_amount_automatically",
+ "fieldtype": "Check",
+ "label": "Write Off Outstanding Amount",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "column_break_74",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "write_off_account",
+ "fieldtype": "Link",
+ "label": "Write Off Account",
+ "options": "Account",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "write_off_cost_center",
+ "fieldtype": "Link",
+ "label": "Write Off Cost Center",
+ "options": "Cost Center",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "terms",
+ "fieldname": "terms_section_break",
+ "fieldtype": "Section Break",
+ "label": "Terms and Conditions",
+ "oldfieldtype": "Section Break"
+ },
+ {
+ "fieldname": "tc_name",
+ "fieldtype": "Link",
+ "label": "Terms",
+ "oldfieldname": "tc_name",
+ "oldfieldtype": "Link",
+ "options": "Terms and Conditions",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "terms",
+ "fieldtype": "Text Editor",
+ "label": "Terms and Conditions Details",
+ "oldfieldname": "terms",
+ "oldfieldtype": "Text Editor"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "edit_printing_settings",
+ "fieldtype": "Section Break",
+ "label": "Printing Settings"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "letter_head",
+ "fieldtype": "Link",
+ "label": "Letter Head",
+ "oldfieldname": "letter_head",
+ "oldfieldtype": "Select",
+ "options": "Letter Head",
+ "print_hide": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "group_same_items",
+ "fieldtype": "Check",
+ "label": "Group same items",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "language",
+ "fieldtype": "Data",
+ "label": "Print Language",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_84",
+ "fieldtype": "Column Break"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "select_print_heading",
+ "fieldtype": "Link",
+ "label": "Print Heading",
+ "no_copy": 1,
+ "oldfieldname": "select_print_heading",
+ "oldfieldtype": "Link",
+ "options": "Print Heading",
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "depends_on": "customer",
+ "fieldname": "more_information",
+ "fieldtype": "Section Break",
+ "label": "More Information"
+ },
+ {
+ "fieldname": "inter_company_invoice_reference",
+ "fieldtype": "Link",
+ "label": "Inter Company Invoice Reference",
+ "options": "Purchase Invoice",
+ "read_only": 1
+ },
+ {
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Customer Group",
+ "options": "Customer Group",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "campaign",
+ "fieldtype": "Link",
+ "label": "Campaign",
+ "oldfieldname": "campaign",
+ "oldfieldtype": "Link",
+ "options": "Campaign",
+ "print_hide": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "is_discounted",
+ "fieldtype": "Check",
+ "label": "Is Discounted",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "col_break23",
+ "fieldtype": "Column Break",
+ "width": "50%"
+ },
+ {
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_standard_filter": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "options": "\nDraft\nReturn\nCredit Note Issued\nConsolidated\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "source",
+ "fieldtype": "Link",
+ "label": "Source",
+ "oldfieldname": "source",
+ "oldfieldtype": "Select",
+ "options": "Lead Source",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "more_info",
+ "fieldtype": "Section Break",
+ "label": "Accounting Details",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-file-text",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "debit_to",
+ "fieldtype": "Link",
+ "label": "Debit To",
+ "oldfieldname": "debit_to",
+ "oldfieldtype": "Link",
+ "options": "Account",
+ "print_hide": 1,
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "party_account_currency",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Party Account Currency",
+ "no_copy": 1,
+ "options": "Currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "default": "No",
+ "fieldname": "is_opening",
+ "fieldtype": "Select",
+ "label": "Is Opening Entry",
+ "oldfieldname": "is_opening",
+ "oldfieldtype": "Select",
+ "options": "No\nYes",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "c_form_applicable",
+ "fieldtype": "Select",
+ "label": "C-Form Applicable",
+ "no_copy": 1,
+ "options": "No\nYes",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "c_form_no",
+ "fieldtype": "Link",
+ "label": "C-Form No",
+ "no_copy": 1,
+ "options": "C-Form",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break8",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "remarks",
+ "fieldtype": "Small Text",
+ "label": "Remarks",
+ "no_copy": 1,
+ "oldfieldname": "remarks",
+ "oldfieldtype": "Text",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "sales_partner",
+ "fieldname": "sales_team_section_break",
+ "fieldtype": "Section Break",
+ "label": "Commission",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-group",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "sales_partner",
+ "fieldtype": "Link",
+ "label": "Sales Partner",
+ "oldfieldname": "sales_partner",
+ "oldfieldtype": "Link",
+ "options": "Sales Partner",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "column_break10",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break",
+ "print_hide": 1,
+ "width": "50%"
+ },
+ {
+ "fieldname": "commission_rate",
+ "fieldtype": "Float",
+ "label": "Commission Rate (%)",
+ "oldfieldname": "commission_rate",
+ "oldfieldtype": "Currency",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "total_commission",
+ "fieldtype": "Currency",
+ "label": "Total Commission",
+ "oldfieldname": "total_commission",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "sales_team",
+ "fieldname": "section_break2",
+ "fieldtype": "Section Break",
+ "label": "Sales Team",
+ "print_hide": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "sales_team",
+ "fieldtype": "Table",
+ "label": "Sales Team1",
+ "oldfieldname": "sales_team",
+ "oldfieldtype": "Table",
+ "options": "Sales Team",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "subscription_section",
+ "fieldtype": "Section Break",
+ "label": "Subscription Section"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "from_date",
+ "fieldtype": "Date",
+ "label": "From Date",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "to_date",
+ "fieldtype": "Date",
+ "label": "To Date",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "column_break_140",
+ "fieldtype": "Column Break"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "auto_repeat",
+ "fieldtype": "Link",
+ "label": "Auto Repeat",
+ "no_copy": 1,
+ "options": "Auto Repeat",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "depends_on": "eval: doc.auto_repeat",
+ "fieldname": "update_auto_repeat_reference",
+ "fieldtype": "Button",
+ "label": "Update Auto Repeat Reference"
+ },
+ {
+ "fieldname": "against_income_account",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Against Income Account",
+ "no_copy": 1,
+ "oldfieldname": "against_income_account",
+ "oldfieldtype": "Small Text",
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "fieldname": "pos_total_qty",
+ "fieldtype": "Float",
+ "hidden": 1,
+ "label": "Total Qty",
+ "print_hide": 1,
+ "print_hide_if_no_value": 1,
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "consolidated_invoice",
+ "fieldtype": "Link",
+ "label": "Consolidated Sales Invoice",
+ "options": "Sales Invoice",
+ "read_only": 1
+ }
+ ],
+ "icon": "fa fa-file-text",
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-05-29 15:08:39.337385",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Invoice",
+ "name_case": "Title Case",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "create": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "permlevel": 1,
+ "read": 1,
+ "role": "Accounts Manager",
+ "write": 1
+ },
+ {
+ "permlevel": 1,
+ "read": 1,
+ "role": "All"
+ }
+ ],
+ "quick_entry": 1,
+ "search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "timeline_field": "customer",
+ "title_field": "title",
+ "track_changes": 1,
+ "track_seen": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
new file mode 100644
index 0000000..8680b71
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -0,0 +1,374 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from erpnext.controllers.selling_controller import SellingController
+from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
+from erpnext.accounts.utils import get_account_currency
+from erpnext.accounts.party import get_party_account, get_due_date
+from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
+ get_loyalty_program_details_with_points, validate_loyalty_points
+
+from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option
+from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
+
+from six import iteritems
+
+class POSInvoice(SalesInvoice):
+ def __init__(self, *args, **kwargs):
+ super(POSInvoice, self).__init__(*args, **kwargs)
+
+ def validate(self):
+ if not cint(self.is_pos):
+ frappe.throw(_("POS Invoice should have {} field checked.").format(frappe.bold("Include Payment")))
+
+ # run on validate method of selling controller
+ super(SalesInvoice, self).validate()
+ self.validate_auto_set_posting_time()
+ self.validate_pos_paid_amount()
+ self.validate_pos_return()
+ self.validate_uom_is_integer("stock_uom", "stock_qty")
+ self.validate_uom_is_integer("uom", "qty")
+ self.validate_debit_to_acc()
+ self.validate_write_off_account()
+ self.validate_change_amount()
+ self.validate_change_account()
+ self.validate_item_cost_centers()
+ self.validate_serialised_or_batched_item()
+ self.validate_stock_availablility()
+ self.validate_return_items()
+ self.set_status()
+ self.set_account_for_mode_of_payment()
+ self.validate_pos()
+ self.verify_payment_amount()
+ self.validate_loyalty_transaction()
+
+ def on_submit(self):
+ # create the loyalty point ledger entry if the customer is enrolled in any loyalty program
+ if self.loyalty_program:
+ self.make_loyalty_point_entry()
+ elif self.is_return and self.return_against and self.loyalty_program:
+ against_psi_doc = frappe.get_doc("POS Invoice", self.return_against)
+ against_psi_doc.delete_loyalty_point_entry()
+ against_psi_doc.make_loyalty_point_entry()
+ if self.redeem_loyalty_points and self.loyalty_points:
+ self.apply_loyalty_points()
+ self.set_status(update=True)
+
+ def on_cancel(self):
+ # run on cancel method of selling controller
+ super(SalesInvoice, self).on_cancel()
+ if self.loyalty_program:
+ self.delete_loyalty_point_entry()
+ elif self.is_return and self.return_against and self.loyalty_program:
+ against_psi_doc = frappe.get_doc("POS Invoice", self.return_against)
+ against_psi_doc.delete_loyalty_point_entry()
+ against_psi_doc.make_loyalty_point_entry()
+
+ def validate_stock_availablility(self):
+ allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
+
+ for d in self.get('items'):
+ if d.serial_no:
+ filters = {
+ "item_code": d.item_code,
+ "warehouse": d.warehouse,
+ "delivery_document_no": "",
+ "sales_invoice": ""
+ }
+ if d.batch_no:
+ filters["batch_no"] = d.batch_no
+ reserved_serial_nos, unreserved_serial_nos = get_pos_reserved_serial_nos(filters)
+ serial_nos = d.serial_no.split("\n")
+ serial_nos = ' '.join(serial_nos).split() # remove whitespaces
+ invalid_serial_nos = []
+ for s in serial_nos:
+ if s in reserved_serial_nos:
+ invalid_serial_nos.append(s)
+
+ if len(invalid_serial_nos):
+ multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
+ frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \
+ Please select valid serial no.".format(d.idx, multiple_nos,
+ frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available"))
+ else:
+ if allow_negative_stock:
+ return
+
+ available_stock = get_stock_availability(d.item_code, d.warehouse)
+ if not (flt(available_stock) > 0):
+ frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.'
+ .format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available"))
+ elif flt(available_stock) < flt(d.qty):
+ frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \
+ Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
+ frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available"))
+
+ def validate_serialised_or_batched_item(self):
+ for d in self.get("items"):
+ serialized = d.get("has_serial_no")
+ batched = d.get("has_batch_no")
+ no_serial_selected = not d.get("serial_no")
+ no_batch_selected = not d.get("batch_no")
+
+
+ if serialized and batched and (no_batch_selected or no_serial_selected):
+ frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.'
+ .format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
+ if serialized and no_serial_selected:
+ frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.'
+ .format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
+ if batched and no_batch_selected:
+ frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.'
+ .format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
+
+ def validate_return_items(self):
+ if not self.get("is_return"): return
+
+ for d in self.get("items"):
+ if d.get("qty") > 0:
+ frappe.throw(_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.")
+ .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
+
+ def validate_pos_paid_amount(self):
+ if len(self.payments) == 0 and self.is_pos:
+ frappe.throw(_("At least one mode of payment is required for POS invoice."))
+
+ def validate_change_account(self):
+ if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
+ frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company))
+
+ def validate_change_amount(self):
+ grand_total = flt(self.rounded_total) or flt(self.grand_total)
+ base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total)
+ if not flt(self.change_amount) and grand_total < flt(self.paid_amount):
+ self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount))
+ self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
+
+ if flt(self.change_amount) and not self.account_for_change_amount:
+ msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
+
+ def verify_payment_amount(self):
+ for entry in self.payments:
+ if not self.is_return and entry.amount < 0:
+ frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
+ if self.is_return and entry.amount > 0:
+ frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
+
+ def validate_pos_return(self):
+ if self.is_pos and self.is_return:
+ total_amount_in_payments = 0
+ for payment in self.payments:
+ total_amount_in_payments += payment.amount
+ invoice_total = self.rounded_total or self.grand_total
+ if total_amount_in_payments < invoice_total:
+ frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
+
+ def validate_loyalty_transaction(self):
+ if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):
+ expense_account, cost_center = frappe.db.get_value('Loyalty Program', self.loyalty_program, ["expense_account", "cost_center"])
+ if not self.loyalty_redemption_account:
+ self.loyalty_redemption_account = expense_account
+ if not self.loyalty_redemption_cost_center:
+ self.loyalty_redemption_cost_center = cost_center
+
+ if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
+ validate_loyalty_points(self, self.loyalty_points)
+
+ def set_status(self, update=False, status=None, update_modified=True):
+ if self.is_new():
+ if self.get('amended_from'):
+ self.status = 'Draft'
+ return
+
+ if not status:
+ if self.docstatus == 2:
+ status = "Cancelled"
+ elif self.docstatus == 1:
+ if self.consolidated_invoice:
+ self.status = "Consolidated"
+ elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) < getdate(nowdate()) and self.is_discounted and self.get_discounting_status()=='Disbursed':
+ self.status = "Overdue and Discounted"
+ elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) < getdate(nowdate()):
+ self.status = "Overdue"
+ elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.is_discounted and self.get_discounting_status()=='Disbursed':
+ 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('POS Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
+ self.status = "Credit Note Issued"
+ elif self.is_return == 1:
+ self.status = "Return"
+ elif flt(self.outstanding_amount)<=0:
+ self.status = "Paid"
+ else:
+ self.status = "Submitted"
+ else:
+ self.status = "Draft"
+
+ if update:
+ self.db_set('status', self.status, update_modified = update_modified)
+
+ def set_pos_fields(self, for_validate=False):
+ """Set retail related fields from POS Profiles"""
+ from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
+ if not self.pos_profile:
+ pos_profile = get_pos_profile(self.company) or {}
+ self.pos_profile = pos_profile.get('name')
+
+ pos = {}
+ if self.pos_profile:
+ pos = frappe.get_doc('POS Profile', self.pos_profile)
+
+ if not self.get('payments') and not for_validate:
+ update_multi_mode_option(self, pos)
+
+ if not self.account_for_change_amount:
+ self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
+
+ if pos:
+ if not for_validate:
+ self.tax_category = pos.get("tax_category")
+
+ if not for_validate and not self.customer:
+ self.customer = pos.customer
+
+ self.ignore_pricing_rule = pos.ignore_pricing_rule
+ if pos.get('account_for_change_amount'):
+ self.account_for_change_amount = pos.get('account_for_change_amount')
+ if pos.get('warehouse'):
+ self.set_warehouse = pos.get('warehouse')
+
+ for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name',
+ 'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
+ 'write_off_cost_center', 'apply_discount_on', 'cost_center'):
+ if (not for_validate) or (for_validate and not self.get(fieldname)):
+ self.set(fieldname, pos.get(fieldname))
+
+ if pos.get("company_address"):
+ self.company_address = pos.get("company_address")
+
+ if self.customer:
+ customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
+ customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
+ selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
+ else:
+ selling_price_list = pos.get('selling_price_list')
+
+ if selling_price_list:
+ self.set('selling_price_list', selling_price_list)
+
+ if not for_validate:
+ self.update_stock = cint(pos.get("update_stock"))
+
+ # set pos values in items
+ for item in self.get("items"):
+ if item.get('item_code'):
+ profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos)
+ for fname, val in iteritems(profile_details):
+ if (not for_validate) or (for_validate and not item.get(fname)):
+ item.set(fname, val)
+
+ # fetch terms
+ if self.tc_name and not self.terms:
+ self.terms = frappe.db.get_value("Terms and Conditions", self.tc_name, "terms")
+
+ # fetch charges
+ if self.taxes_and_charges and not len(self.get("taxes")):
+ self.set_taxes()
+
+ return pos
+
+ def set_missing_values(self, for_validate=False):
+ pos = self.set_pos_fields(for_validate)
+
+ if not self.debit_to:
+ self.debit_to = get_party_account("Customer", self.customer, self.company)
+ self.party_account_currency = frappe.db.get_value("Account", self.debit_to, "account_currency", cache=True)
+ if not self.due_date and self.customer:
+ self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
+
+ super(SalesInvoice, self).set_missing_values(for_validate)
+
+ print_format = pos.get("print_format") if pos else None
+ if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')):
+ print_format = 'POS Invoice'
+
+ if pos:
+ return {
+ "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"),
+ "allow_print_before_pay": pos.get("allow_print_before_pay")
+ }
+
+ def set_account_for_mode_of_payment(self):
+ self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
+ for pay in self.payments:
+ if not pay.account:
+ pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
+
+@frappe.whitelist()
+def get_stock_availability(item_code, warehouse):
+ latest_sle = frappe.db.sql("""select qty_after_transaction
+ from `tabStock Ledger Entry`
+ where item_code = %s and warehouse = %s
+ order by posting_date desc, posting_time desc
+ limit 1""", (item_code, warehouse), as_dict=1)
+
+ pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty
+ from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
+ where p.name = p_item.parent
+ and p.consolidated_invoice is NULL
+ and p.docstatus = 1
+ and p_item.docstatus = 1
+ and p_item.item_code = %s
+ and p_item.warehouse = %s
+ """, (item_code, warehouse), as_dict=1)
+
+ sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
+ pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
+
+ if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty:
+ return sle_qty - pos_sales_qty
+ else:
+ # when sle_qty is 0
+ # when sle_qty > 0 and pos_sales_qty is 0
+ return sle_qty
+
+@frappe.whitelist()
+def make_sales_return(source_name, target_doc=None):
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+ return make_return_doc("POS Invoice", source_name, target_doc)
+
+@frappe.whitelist()
+def make_merge_log(invoices):
+ import json
+ from six import string_types
+
+ if isinstance(invoices, string_types):
+ invoices = json.loads(invoices)
+
+ if len(invoices) == 0:
+ frappe.throw(_('Atleast one invoice has to be selected.'))
+
+ merge_log = frappe.new_doc("POS Invoice Merge Log")
+ merge_log.posting_date = getdate(nowdate())
+ for inv in invoices:
+ inv_data = frappe.db.get_values("POS Invoice", inv.get('name'),
+ ["customer", "posting_date", "grand_total"], as_dict=1)[0]
+ merge_log.customer = inv_data.customer
+ merge_log.append("pos_invoices", {
+ 'pos_invoice': inv.get('name'),
+ 'customer': inv_data.customer,
+ 'posting_date': inv_data.posting_date,
+ 'grand_total': inv_data.grand_total
+ })
+
+ if merge_log.get('pos_invoices'):
+ return merge_log.as_dict()
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice_list.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice_list.js
new file mode 100644
index 0000000..2dbf2a4
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice_list.js
@@ -0,0 +1,42 @@
+// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+// License: GNU General Public License v3. See license.txt
+
+// render
+frappe.listview_settings['POS Invoice'] = {
+ add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company",
+ "currency", "is_return"],
+ get_indicator: function(doc) {
+ var status_color = {
+ "Draft": "red",
+ "Unpaid": "orange",
+ "Paid": "green",
+ "Submitted": "blue",
+ "Consolidated": "green",
+ "Return": "darkgrey",
+ "Unpaid and Discounted": "orange",
+ "Overdue and Discounted": "red",
+ "Overdue": "red"
+
+ };
+ return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
+ },
+ right_column: "grand_total",
+ onload: function(me) {
+ me.page.add_action_item('Make Merge Log', function() {
+ const invoices = me.get_checked_items();
+ frappe.call({
+ method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_merge_log",
+ freeze: true,
+ args:{
+ "invoices": invoices
+ },
+ callback: function (r) {
+ if (r.message) {
+ var doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
+ }
+ }
+ });
+ });
+ },
+};
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
new file mode 100644
index 0000000..f295725
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -0,0 +1,324 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest, copy, time
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
+from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
+
+class TestPOSInvoice(unittest.TestCase):
+ def test_timestamp_change(self):
+ w = create_pos_invoice(do_not_save=1)
+ w.docstatus = 0
+ w.insert()
+
+ w2 = frappe.get_doc(w.doctype, w.name)
+
+ import time
+ time.sleep(1)
+ w.save()
+
+ import time
+ time.sleep(1)
+ self.assertRaises(frappe.TimestampMismatchError, w2.save)
+
+ def test_change_naming_series(self):
+ inv = create_pos_invoice(do_not_submit=1)
+ inv.naming_series = 'TEST-'
+
+ self.assertRaises(frappe.CannotChangeConstantError, inv.save)
+
+ def test_discount_and_inclusive_tax(self):
+ inv = create_pos_invoice(qty=100, rate=50, do_not_save=1)
+ inv.append("taxes", {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Service Tax - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Service Tax",
+ "rate": 14,
+ 'included_in_print_rate': 1
+ })
+ inv.insert()
+
+ self.assertEqual(inv.net_total, 4385.96)
+ self.assertEqual(inv.grand_total, 5000)
+
+ inv.reload()
+
+ inv.discount_amount = 100
+ inv.apply_discount_on = 'Net Total'
+ inv.payment_schedule = []
+
+ inv.save()
+
+ self.assertEqual(inv.net_total, 4285.96)
+ self.assertEqual(inv.grand_total, 4885.99)
+
+ inv.reload()
+
+ inv.discount_amount = 100
+ inv.apply_discount_on = 'Grand Total'
+ inv.payment_schedule = []
+
+ inv.save()
+
+ self.assertEqual(inv.net_total, 4298.25)
+ self.assertEqual(inv.grand_total, 4900.00)
+
+ def test_tax_calculation_with_multiple_items(self):
+ inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=True)
+ item_row = inv.get("items")[0]
+ for qty in (54, 288, 144, 430):
+ item_row_copy = copy.deepcopy(item_row)
+ item_row_copy.qty = qty
+ inv.append("items", item_row_copy)
+
+ inv.append("taxes", {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 19
+ })
+ inv.insert()
+
+ self.assertEqual(inv.net_total, 4600)
+
+ self.assertEqual(inv.get("taxes")[0].tax_amount, 874.0)
+ self.assertEqual(inv.get("taxes")[0].total, 5474.0)
+
+ self.assertEqual(inv.grand_total, 5474.0)
+
+ def test_tax_calculation_with_item_tax_template(self):
+ inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=1)
+ item_row = inv.get("items")[0]
+
+ add_items = [
+ (54, '_Test Account Excise Duty @ 12'),
+ (288, '_Test Account Excise Duty @ 15'),
+ (144, '_Test Account Excise Duty @ 20'),
+ (430, '_Test Item Tax Template 1')
+ ]
+ for qty, item_tax_template in add_items:
+ item_row_copy = copy.deepcopy(item_row)
+ item_row_copy.qty = qty
+ item_row_copy.item_tax_template = item_tax_template
+ inv.append("items", item_row_copy)
+
+ inv.append("taxes", {
+ "account_head": "_Test Account Excise Duty - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Excise Duty",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 11
+ })
+ inv.append("taxes", {
+ "account_head": "_Test Account Education Cess - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Education Cess",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 0
+ })
+ inv.append("taxes", {
+ "account_head": "_Test Account S&H Education Cess - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "S&H Education Cess",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 3
+ })
+ inv.insert()
+
+ self.assertEqual(inv.net_total, 4600)
+
+ self.assertEqual(inv.get("taxes")[0].tax_amount, 502.41)
+ self.assertEqual(inv.get("taxes")[0].total, 5102.41)
+
+ self.assertEqual(inv.get("taxes")[1].tax_amount, 197.80)
+ self.assertEqual(inv.get("taxes")[1].total, 5300.21)
+
+ self.assertEqual(inv.get("taxes")[2].tax_amount, 375.36)
+ self.assertEqual(inv.get("taxes")[2].total, 5675.57)
+
+ self.assertEqual(inv.grand_total, 5675.57)
+ self.assertEqual(inv.rounding_adjustment, 0.43)
+ self.assertEqual(inv.rounded_total, 5676.0)
+
+ def test_tax_calculation_with_multiple_items_and_discount(self):
+ inv = create_pos_invoice(qty=1, rate=75, do_not_save=True)
+ item_row = inv.get("items")[0]
+ for rate in (500, 200, 100, 50, 50):
+ item_row_copy = copy.deepcopy(item_row)
+ item_row_copy.price_list_rate = rate
+ item_row_copy.rate = rate
+ inv.append("items", item_row_copy)
+
+ inv.apply_discount_on = "Net Total"
+ inv.discount_amount = 75.0
+
+ inv.append("taxes", {
+ "account_head": "_Test Account VAT - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "VAT",
+ "doctype": "Sales Taxes and Charges",
+ "rate": 24
+ })
+ inv.insert()
+
+ self.assertEqual(inv.total, 975)
+ self.assertEqual(inv.net_total, 900)
+
+ self.assertEqual(inv.get("taxes")[0].tax_amount, 216.0)
+ self.assertEqual(inv.get("taxes")[0].total, 1116.0)
+
+ self.assertEqual(inv.grand_total, 1116.0)
+
+ def test_pos_returns_with_repayment(self):
+ pos = create_pos_invoice(qty = 10, do_not_save=True)
+
+ pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
+ pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
+ pos.insert()
+ pos.submit()
+
+ pos_return = make_sales_return(pos.name)
+
+ pos_return.insert()
+ pos_return.submit()
+
+ self.assertEqual(pos_return.get('payments')[0].amount, -500)
+ self.assertEqual(pos_return.get('payments')[1].amount, -500)
+
+ def test_pos_change_amount(self):
+ pos = create_pos_invoice(company= "_Test Company", debit_to="Debtors - _TC",
+ income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
+ cost_center = "Main - _TC", do_not_save=True)
+
+ pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50})
+ pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60})
+
+ pos.insert()
+ pos.submit()
+
+ self.assertEqual(pos.grand_total, 105.0)
+ self.assertEqual(pos.change_amount, 5.0)
+
+ def test_without_payment(self):
+ inv = create_pos_invoice(do_not_save=1)
+ # Check that the invoice cannot be submitted without payments
+ inv.payments = []
+ self.assertRaises(frappe.ValidationError, inv.insert)
+
+ def test_serialized_item_transaction(self):
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+ se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
+ serial_nos = get_serial_nos(se.get("items")[0].serial_no)
+
+ pos = create_pos_invoice(item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
+ pos.get("items")[0].serial_no = serial_nos[0]
+ pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
+
+ pos.insert()
+ pos.submit()
+
+ pos2 = create_pos_invoice(item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
+ pos2.get("items")[0].serial_no = serial_nos[0]
+ pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
+
+ self.assertRaises(frappe.ValidationError, pos2.insert)
+
+ def test_loyalty_points(self):
+ from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
+ from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
+
+ create_records()
+ frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
+ before_lp_details = get_loyalty_program_details_with_points("Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty")
+
+ inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000)
+
+ lpe = frappe.get_doc('Loyalty Point Entry', {'invoice_type': 'POS Invoice', 'invoice': inv.name, 'customer': inv.customer})
+ after_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program)
+
+ self.assertEqual(inv.get('loyalty_program'), "Test Single Loyalty")
+ self.assertEqual(lpe.loyalty_points, 10)
+ self.assertEqual(after_lp_details.loyalty_points, before_lp_details.loyalty_points + 10)
+
+ inv.cancel()
+ after_cancel_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program)
+ self.assertEqual(after_cancel_lp_details.loyalty_points, before_lp_details.loyalty_points)
+
+ def test_loyalty_points_redeemption(self):
+ from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
+ # add 10 loyalty points
+ create_pos_invoice(customer="Test Loyalty Customer", rate=10000)
+
+ before_lp_details = get_loyalty_program_details_with_points("Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty")
+
+ inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1)
+ inv.redeem_loyalty_points = 1
+ inv.loyalty_points = before_lp_details.loyalty_points
+ inv.loyalty_amount = inv.loyalty_points * before_lp_details.conversion_factor
+ inv.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 10000 - inv.loyalty_amount})
+ inv.paid_amount = 10000
+ inv.submit()
+
+ after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program)
+ self.assertEqual(after_redeem_lp_details.loyalty_points, 9)
+
+def create_pos_invoice(**args):
+ args = frappe._dict(args)
+ pos_profile = None
+ if not args.pos_profile:
+ pos_profile = make_pos_profile()
+ pos_profile.save()
+
+ pos_inv = frappe.new_doc("POS Invoice")
+ pos_inv.update_stock = 1
+ pos_inv.is_pos = 1
+ pos_inv.pos_profile = args.pos_profile or pos_profile.name
+
+ pos_inv.set_missing_values()
+
+ if args.posting_date:
+ pos_inv.set_posting_time = 1
+ pos_inv.posting_date = args.posting_date or frappe.utils.nowdate()
+
+ pos_inv.company = args.company or "_Test Company"
+ pos_inv.customer = args.customer or "_Test Customer"
+ pos_inv.debit_to = args.debit_to or "Debtors - _TC"
+ pos_inv.is_return = args.is_return
+ pos_inv.return_against = args.return_against
+ pos_inv.currency=args.currency or "INR"
+ pos_inv.conversion_rate = args.conversion_rate or 1
+ pos_inv.account_for_change_amount = "Cash - _TC"
+
+ pos_inv.append("items", {
+ "item_code": args.item or args.item_code or "_Test Item",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty or 1,
+ "rate": args.rate if args.get("rate") is not None else 100,
+ "income_account": args.income_account or "Sales - _TC",
+ "expense_account": args.expense_account or "Cost of Goods Sold - _TC",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
+ "serial_no": args.serial_no
+ })
+
+ if not args.do_not_save:
+ pos_inv.insert()
+ if not args.do_not_submit:
+ pos_inv.submit()
+ else:
+ pos_inv.payment_schedule = []
+ else:
+ pos_inv.payment_schedule = []
+
+ return pos_inv
\ No newline at end of file
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/pos_invoice_item/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/pos_invoice_item/__init__.py
diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
new file mode 100644
index 0000000..2b6e7de
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
@@ -0,0 +1,805 @@
+{
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2020-01-27 13:04:55.229516",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "barcode",
+ "item_code",
+ "col_break1",
+ "item_name",
+ "customer_item_code",
+ "description_section",
+ "description",
+ "item_group",
+ "brand",
+ "image_section",
+ "image",
+ "image_view",
+ "quantity_and_rate",
+ "qty",
+ "stock_uom",
+ "col_break2",
+ "uom",
+ "conversion_factor",
+ "stock_qty",
+ "section_break_17",
+ "price_list_rate",
+ "base_price_list_rate",
+ "discount_and_margin",
+ "margin_type",
+ "margin_rate_or_amount",
+ "rate_with_margin",
+ "column_break_19",
+ "discount_percentage",
+ "discount_amount",
+ "base_rate_with_margin",
+ "section_break1",
+ "rate",
+ "amount",
+ "item_tax_template",
+ "col_break3",
+ "base_rate",
+ "base_amount",
+ "pricing_rules",
+ "is_free_item",
+ "section_break_21",
+ "net_rate",
+ "net_amount",
+ "column_break_24",
+ "base_net_rate",
+ "base_net_amount",
+ "drop_ship",
+ "delivered_by_supplier",
+ "accounting",
+ "income_account",
+ "is_fixed_asset",
+ "asset",
+ "finance_book",
+ "col_break4",
+ "expense_account",
+ "deferred_revenue",
+ "deferred_revenue_account",
+ "service_stop_date",
+ "enable_deferred_revenue",
+ "column_break_50",
+ "service_start_date",
+ "service_end_date",
+ "section_break_18",
+ "weight_per_unit",
+ "total_weight",
+ "column_break_21",
+ "weight_uom",
+ "warehouse_and_reference",
+ "warehouse",
+ "target_warehouse",
+ "quality_inspection",
+ "batch_no",
+ "col_break5",
+ "allow_zero_valuation_rate",
+ "serial_no",
+ "item_tax_rate",
+ "actual_batch_qty",
+ "actual_qty",
+ "edit_references",
+ "sales_order",
+ "so_detail",
+ "column_break_74",
+ "delivery_note",
+ "dn_detail",
+ "delivered_qty",
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break",
+ "project",
+ "section_break_54",
+ "page_break"
+ ],
+ "fields": [
+ {
+ "fieldname": "barcode",
+ "fieldtype": "Data",
+ "label": "Barcode",
+ "print_hide": 1
+ },
+ {
+ "bold": 1,
+ "columns": 4,
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item",
+ "oldfieldname": "item_code",
+ "oldfieldtype": "Link",
+ "options": "Item",
+ "search_index": 1
+ },
+ {
+ "fieldname": "col_break1",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "label": "Item Name",
+ "oldfieldname": "item_name",
+ "oldfieldtype": "Data",
+ "print_hide": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "customer_item_code",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Customer's Item Code",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "description_section",
+ "fieldtype": "Section Break",
+ "label": "Description"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Text Editor",
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Text",
+ "print_width": "200px",
+ "reqd": 1,
+ "width": "200px"
+ },
+ {
+ "fieldname": "item_group",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Item Group",
+ "oldfieldname": "item_group",
+ "oldfieldtype": "Link",
+ "options": "Item Group",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "brand",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Brand Name",
+ "oldfieldname": "brand",
+ "oldfieldtype": "Data",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "image_section",
+ "fieldtype": "Section Break",
+ "label": "Image"
+ },
+ {
+ "fieldname": "image",
+ "fieldtype": "Attach",
+ "hidden": 1,
+ "label": "Image"
+ },
+ {
+ "fieldname": "image_view",
+ "fieldtype": "Image",
+ "label": "Image View",
+ "options": "image",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "quantity_and_rate",
+ "fieldtype": "Section Break"
+ },
+ {
+ "bold": 1,
+ "columns": 2,
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Quantity",
+ "oldfieldname": "qty",
+ "oldfieldtype": "Currency"
+ },
+ {
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "label": "Stock UOM",
+ "options": "UOM",
+ "read_only": 1
+ },
+ {
+ "fieldname": "col_break2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "uom",
+ "fieldtype": "Link",
+ "label": "UOM",
+ "options": "UOM",
+ "reqd": 1
+ },
+ {
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "label": "UOM Conversion Factor",
+ "print_hide": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "stock_qty",
+ "fieldtype": "Float",
+ "label": "Qty as per Stock UOM",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_17",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "price_list_rate",
+ "fieldtype": "Currency",
+ "label": "Price List Rate",
+ "oldfieldname": "ref_rate",
+ "oldfieldtype": "Currency",
+ "options": "currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_price_list_rate",
+ "fieldtype": "Currency",
+ "label": "Price List Rate (Company Currency)",
+ "oldfieldname": "base_ref_rate",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "discount_and_margin",
+ "fieldtype": "Section Break",
+ "label": "Discount and Margin"
+ },
+ {
+ "depends_on": "price_list_rate",
+ "fieldname": "margin_type",
+ "fieldtype": "Select",
+ "label": "Margin Type",
+ "options": "\nPercentage\nAmount",
+ "print_hide": 1
+ },
+ {
+ "depends_on": "eval:doc.margin_type && doc.price_list_rate",
+ "fieldname": "margin_rate_or_amount",
+ "fieldtype": "Float",
+ "label": "Margin Rate or Amount",
+ "print_hide": 1
+ },
+ {
+ "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
+ "fieldname": "rate_with_margin",
+ "fieldtype": "Currency",
+ "label": "Rate With Margin",
+ "options": "currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_19",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "price_list_rate",
+ "fieldname": "discount_percentage",
+ "fieldtype": "Percent",
+ "label": "Discount (%) on Price List Rate with Margin",
+ "oldfieldname": "adj_rate",
+ "oldfieldtype": "Float",
+ "precision": "2",
+ "print_hide": 1
+ },
+ {
+ "depends_on": "price_list_rate",
+ "fieldname": "discount_amount",
+ "fieldtype": "Currency",
+ "label": "Discount Amount",
+ "options": "currency"
+ },
+ {
+ "depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
+ "fieldname": "base_rate_with_margin",
+ "fieldtype": "Currency",
+ "label": "Rate With Margin (Company Currency)",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break1",
+ "fieldtype": "Section Break"
+ },
+ {
+ "bold": 1,
+ "columns": 2,
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Rate",
+ "oldfieldname": "export_rate",
+ "oldfieldtype": "Currency",
+ "options": "currency",
+ "reqd": 1
+ },
+ {
+ "columns": 2,
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Amount",
+ "oldfieldname": "export_amount",
+ "oldfieldtype": "Currency",
+ "options": "currency",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "item_tax_template",
+ "fieldtype": "Link",
+ "label": "Item Tax Template",
+ "options": "Item Tax Template",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "col_break3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "base_rate",
+ "fieldtype": "Currency",
+ "label": "Rate (Company Currency)",
+ "oldfieldname": "basic_rate",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "base_amount",
+ "fieldtype": "Currency",
+ "label": "Amount (Company Currency)",
+ "oldfieldname": "amount",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "pricing_rules",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Pricing Rules",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "is_free_item",
+ "fieldtype": "Check",
+ "label": "Is Free Item",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_21",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "net_rate",
+ "fieldtype": "Currency",
+ "label": "Net Rate",
+ "options": "currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "net_amount",
+ "fieldtype": "Currency",
+ "label": "Net Amount",
+ "options": "currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_24",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "base_net_rate",
+ "fieldtype": "Currency",
+ "label": "Net Rate (Company Currency)",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_net_amount",
+ "fieldtype": "Currency",
+ "label": "Net Amount (Company Currency)",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:doc.delivered_by_supplier==1",
+ "fieldname": "drop_ship",
+ "fieldtype": "Section Break",
+ "label": "Drop Ship"
+ },
+ {
+ "default": "0",
+ "fieldname": "delivered_by_supplier",
+ "fieldtype": "Check",
+ "label": "Delivered By Supplier",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "accounting",
+ "fieldtype": "Section Break",
+ "label": "Accounting Details"
+ },
+ {
+ "fieldname": "income_account",
+ "fieldtype": "Link",
+ "label": "Income Account",
+ "oldfieldname": "income_account",
+ "oldfieldtype": "Link",
+ "options": "Account",
+ "print_hide": 1,
+ "print_width": "120px",
+ "reqd": 1,
+ "width": "120px"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_fixed_asset",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Is Fixed Asset",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "asset",
+ "fieldtype": "Link",
+ "label": "Asset",
+ "no_copy": 1,
+ "options": "Asset"
+ },
+ {
+ "depends_on": "asset",
+ "fieldname": "finance_book",
+ "fieldtype": "Link",
+ "label": "Finance Book",
+ "options": "Finance Book"
+ },
+ {
+ "fieldname": "col_break4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "expense_account",
+ "fieldtype": "Link",
+ "label": "Expense Account",
+ "options": "Account",
+ "print_hide": 1,
+ "width": "120px"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "deferred_revenue",
+ "fieldtype": "Section Break",
+ "label": "Deferred Revenue"
+ },
+ {
+ "depends_on": "enable_deferred_revenue",
+ "fieldname": "deferred_revenue_account",
+ "fieldtype": "Link",
+ "label": "Deferred Revenue Account",
+ "options": "Account"
+ },
+ {
+ "allow_on_submit": 1,
+ "depends_on": "enable_deferred_revenue",
+ "fieldname": "service_stop_date",
+ "fieldtype": "Date",
+ "label": "Service Stop Date",
+ "no_copy": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_deferred_revenue",
+ "fieldtype": "Check",
+ "label": "Enable Deferred Revenue"
+ },
+ {
+ "fieldname": "column_break_50",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "enable_deferred_revenue",
+ "fieldname": "service_start_date",
+ "fieldtype": "Date",
+ "label": "Service Start Date",
+ "no_copy": 1
+ },
+ {
+ "depends_on": "enable_deferred_revenue",
+ "fieldname": "service_end_date",
+ "fieldtype": "Date",
+ "label": "Service End Date",
+ "no_copy": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "section_break_18",
+ "fieldtype": "Section Break",
+ "label": "Item Weight Details"
+ },
+ {
+ "fieldname": "weight_per_unit",
+ "fieldtype": "Float",
+ "label": "Weight Per Unit",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_weight",
+ "fieldtype": "Float",
+ "label": "Total Weight",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_21",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "weight_uom",
+ "fieldtype": "Link",
+ "label": "Weight UOM",
+ "options": "UOM",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:doc.serial_no || doc.batch_no",
+ "fieldname": "warehouse_and_reference",
+ "fieldtype": "Section Break",
+ "label": "Stock Details"
+ },
+ {
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Warehouse",
+ "oldfieldname": "warehouse",
+ "oldfieldtype": "Link",
+ "options": "Warehouse",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "target_warehouse",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "ignore_user_permissions": 1,
+ "label": "Customer Warehouse (Optional)",
+ "no_copy": 1,
+ "options": "Warehouse",
+ "print_hide": 1
+ },
+ {
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "quality_inspection",
+ "fieldtype": "Link",
+ "label": "Quality Inspection",
+ "options": "Quality Inspection"
+ },
+ {
+ "fieldname": "batch_no",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Batch No",
+ "options": "Batch",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "col_break5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "allow_zero_valuation_rate",
+ "fieldtype": "Check",
+ "label": "Allow Zero Valuation Rate",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "serial_no",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Serial No",
+ "oldfieldname": "serial_no",
+ "oldfieldtype": "Small Text"
+ },
+ {
+ "fieldname": "item_tax_rate",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Item Tax Rate",
+ "oldfieldname": "item_tax_rate",
+ "oldfieldtype": "Small Text",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "actual_batch_qty",
+ "fieldtype": "Float",
+ "label": "Available Batch Qty at Warehouse",
+ "no_copy": 1,
+ "print_hide": 1,
+ "print_width": "150px",
+ "read_only": 1,
+ "width": "150px"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "actual_qty",
+ "fieldtype": "Float",
+ "label": "Available Qty at Warehouse",
+ "oldfieldname": "actual_qty",
+ "oldfieldtype": "Currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "edit_references",
+ "fieldtype": "Section Break",
+ "label": "References"
+ },
+ {
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "label": "Sales Order",
+ "no_copy": 1,
+ "oldfieldname": "sales_order",
+ "oldfieldtype": "Link",
+ "options": "Sales Order",
+ "print_hide": 1,
+ "read_only": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "so_detail",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Sales Order Item",
+ "no_copy": 1,
+ "oldfieldname": "so_detail",
+ "oldfieldtype": "Data",
+ "print_hide": 1,
+ "read_only": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "column_break_74",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "delivery_note",
+ "fieldtype": "Link",
+ "label": "Delivery Note",
+ "no_copy": 1,
+ "oldfieldname": "delivery_note",
+ "oldfieldtype": "Link",
+ "options": "Delivery Note",
+ "print_hide": 1,
+ "read_only": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "dn_detail",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Delivery Note Item",
+ "no_copy": 1,
+ "oldfieldname": "dn_detail",
+ "oldfieldtype": "Data",
+ "print_hide": 1,
+ "read_only": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "delivered_qty",
+ "fieldtype": "Float",
+ "label": "Delivered Qty",
+ "oldfieldname": "delivered_qty",
+ "oldfieldtype": "Currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "default": ":Company",
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "oldfieldname": "cost_center",
+ "oldfieldtype": "Link",
+ "options": "Cost Center",
+ "print_hide": 1,
+ "print_width": "120px",
+ "reqd": 1,
+ "width": "120px"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_54",
+ "fieldtype": "Section Break"
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "page_break",
+ "fieldtype": "Check",
+ "label": "Page Break",
+ "no_copy": 1,
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-07-22 13:40:34.418346",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Invoice Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py
new file mode 100644
index 0000000..92ce61b
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class POSInvoiceItem(Document):
+ pass
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/pos_invoice_merge_log/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/pos_invoice_merge_log/__init__.py
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js
new file mode 100644
index 0000000..cd08efc
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('POS Invoice Merge Log', {
+ setup: function(frm) {
+ frm.set_query("pos_invoice", "pos_invoices", doc => {
+ return{
+ filters: {
+ 'docstatus': 1,
+ 'customer': doc.customer,
+ 'consolidated_invoice': ''
+ }
+ }
+ });
+ }
+});
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json
new file mode 100644
index 0000000..8f97639
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json
@@ -0,0 +1,147 @@
+{
+ "actions": [],
+ "creation": "2020-01-28 11:56:33.945372",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "posting_date",
+ "customer",
+ "section_break_3",
+ "pos_invoices",
+ "references_section",
+ "consolidated_invoice",
+ "column_break_7",
+ "consolidated_credit_note",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Posting Date",
+ "reqd": 1
+ },
+ {
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Customer",
+ "options": "Customer",
+ "reqd": 1
+ },
+ {
+ "fieldname": "section_break_3",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "pos_invoices",
+ "fieldtype": "Table",
+ "label": "POS Invoices",
+ "options": "POS Invoice Reference",
+ "reqd": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "references_section",
+ "fieldtype": "Section Break",
+ "label": "References"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "POS Invoice Merge Log",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "consolidated_invoice",
+ "fieldtype": "Link",
+ "label": "Consolidated Sales Invoice",
+ "options": "Sales Invoice",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "consolidated_credit_note",
+ "fieldtype": "Link",
+ "label": "Consolidated Credit Note",
+ "options": "Sales Invoice",
+ "read_only": 1
+ }
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-05-29 15:08:41.317100",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Invoice Merge Log",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "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
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Administrator",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
new file mode 100644
index 0000000..00dbad5
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
+from frappe.model.document import Document
+from frappe.model.mapper import map_doc
+from frappe.model import default_fields
+
+from six import iteritems
+
+class POSInvoiceMergeLog(Document):
+ def validate(self):
+ self.validate_customer()
+ self.validate_pos_invoice_status()
+
+ def validate_customer(self):
+ for d in self.pos_invoices:
+ if d.customer != self.customer:
+ frappe.throw(_("Row #{}: POS Invoice {} is not against customer {}").format(d.idx, d.pos_invoice, self.customer))
+
+ def validate_pos_invoice_status(self):
+ for d in self.pos_invoices:
+ status, docstatus = frappe.db.get_value('POS Invoice', d.pos_invoice, ['status', 'docstatus'])
+ if docstatus != 1:
+ frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice))
+ if status in ['Consolidated']:
+ frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status))
+
+ def on_submit(self):
+ pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
+
+ returns = [d for d in pos_invoice_docs if d.get('is_return') == 1]
+ sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
+
+ sales_invoice = self.process_merging_into_sales_invoice(sales)
+
+ if len(returns):
+ credit_note = self.process_merging_into_credit_note(returns)
+ else:
+ credit_note = ""
+
+ self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
+
+ self.update_pos_invoices(sales_invoice, credit_note)
+
+ def process_merging_into_sales_invoice(self, data):
+ sales_invoice = self.get_new_sales_invoice()
+
+ sales_invoice = self.merge_pos_invoice_into(sales_invoice, data)
+
+ sales_invoice.is_consolidated = 1
+ sales_invoice.save()
+ sales_invoice.submit()
+ self.consolidated_invoice = sales_invoice.name
+
+ return sales_invoice.name
+
+ def process_merging_into_credit_note(self, data):
+ credit_note = self.get_new_sales_invoice()
+ credit_note.is_return = 1
+
+ credit_note = self.merge_pos_invoice_into(credit_note, data)
+
+ credit_note.is_consolidated = 1
+ # TODO: return could be against multiple sales invoice which could also have been consolidated?
+ credit_note.return_against = self.consolidated_invoice
+ credit_note.save()
+ credit_note.submit()
+ self.consolidated_credit_note = credit_note.name
+
+ return credit_note.name
+
+ def merge_pos_invoice_into(self, invoice, data):
+ items, payments, taxes = [], [], []
+ loyalty_amount_sum, loyalty_points_sum = 0, 0
+ for doc in data:
+ map_doc(doc, invoice, table_map={ "doctype": invoice.doctype })
+
+ if doc.redeem_loyalty_points:
+ invoice.loyalty_redemption_account = doc.loyalty_redemption_account
+ invoice.loyalty_redemption_cost_center = doc.loyalty_redemption_cost_center
+ loyalty_points_sum += doc.loyalty_points
+ loyalty_amount_sum += doc.loyalty_amount
+
+ for item in doc.get('items'):
+ items.append(item)
+
+ for tax in doc.get('taxes'):
+ found = False
+ for t in taxes:
+ if t.account_head == tax.account_head and t.cost_center == tax.cost_center and t.rate == tax.rate:
+ t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount)
+ t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount)
+ found = True
+ if not found:
+ tax.charge_type = 'Actual'
+ taxes.append(tax)
+
+ for payment in doc.get('payments'):
+ found = False
+ for pay in payments:
+ if pay.account == payment.account and pay.mode_of_payment == payment.mode_of_payment:
+ pay.amount = flt(pay.amount) + flt(payment.amount)
+ pay.base_amount = flt(pay.base_amount) + flt(payment.base_amount)
+ found = True
+ if not found:
+ payments.append(payment)
+
+ if loyalty_points_sum:
+ invoice.redeem_loyalty_points = 1
+ invoice.loyalty_points = loyalty_points_sum
+ invoice.loyalty_amount = loyalty_amount_sum
+
+ invoice.set('items', items)
+ invoice.set('payments', payments)
+ invoice.set('taxes', taxes)
+
+ return invoice
+
+ def get_new_sales_invoice(self):
+ sales_invoice = frappe.new_doc('Sales Invoice')
+ sales_invoice.customer = self.customer
+ sales_invoice.is_pos = 1
+ # date can be pos closing date?
+ sales_invoice.posting_date = getdate(nowdate())
+
+ return sales_invoice
+
+ def update_pos_invoices(self, sales_invoice, credit_note):
+ for d in self.pos_invoices:
+ doc = frappe.get_doc('POS Invoice', d.pos_invoice)
+ if not doc.is_return:
+ doc.update({'consolidated_invoice': sales_invoice})
+ else:
+ doc.update({'consolidated_invoice': credit_note})
+ doc.set_status(update=True)
+ doc.save()
+
+def get_all_invoices():
+ filters = {
+ 'consolidated_invoice': [ 'in', [ '', None ]],
+ 'status': ['not in', ['Consolidated']],
+ 'docstatus': 1
+ }
+ pos_invoices = frappe.db.get_all('POS Invoice', filters=filters,
+ fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer'])
+
+ return pos_invoices
+
+def get_invoices_customer_map(pos_invoices):
+ # pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] }
+ pos_invoice_customer_map = {}
+ for invoice in pos_invoices:
+ customer = invoice.get('customer')
+ pos_invoice_customer_map.setdefault(customer, [])
+ pos_invoice_customer_map[customer].append(invoice)
+
+ return pos_invoice_customer_map
+
+def merge_pos_invoices(pos_invoices=[]):
+ if not pos_invoices:
+ pos_invoices = get_all_invoices()
+
+ pos_invoice_map = get_invoices_customer_map(pos_invoices)
+ create_merge_logs(pos_invoice_map)
+
+def create_merge_logs(pos_invoice_customer_map):
+ for customer, invoices in iteritems(pos_invoice_customer_map):
+ merge_log = frappe.new_doc('POS Invoice Merge Log')
+ merge_log.posting_date = getdate(nowdate())
+ merge_log.customer = customer
+
+ merge_log.set('pos_invoices', invoices)
+ merge_log.save(ignore_permissions=True)
+ merge_log.submit()
+
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
new file mode 100644
index 0000000..0f34272
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
+from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
+from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
+from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
+
+class TestPOSInvoiceMergeLog(unittest.TestCase):
+ def test_consolidated_invoice_creation(self):
+ frappe.db.sql("delete from `tabPOS Invoice`")
+
+ test_user, pos_profile = init_user_and_profile()
+
+ pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
+ pos_inv.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
+ })
+ pos_inv.submit()
+
+ pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
+ pos_inv2.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
+ })
+ pos_inv2.submit()
+
+ pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
+ pos_inv3.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
+ })
+ pos_inv3.submit()
+
+ merge_pos_invoices()
+
+ pos_inv.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
+
+ pos_inv3.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
+
+ self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
+
+ frappe.set_user("Administrator")
+ frappe.db.sql("delete from `tabPOS Profile`")
+ frappe.db.sql("delete from `tabPOS Invoice`")
+
+ def test_consolidated_credit_note_creation(self):
+ frappe.db.sql("delete from `tabPOS Invoice`")
+
+ test_user, pos_profile = init_user_and_profile()
+
+ pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
+ pos_inv.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
+ })
+ pos_inv.submit()
+
+ pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
+ pos_inv2.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
+ })
+ pos_inv2.submit()
+
+ pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
+ pos_inv3.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
+ })
+ pos_inv3.submit()
+
+ pos_inv_cn = make_sales_return(pos_inv.name)
+ pos_inv_cn.set("payments", [])
+ pos_inv_cn.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
+ })
+ pos_inv_cn.paid_amount = -300
+ pos_inv_cn.submit()
+
+ merge_pos_invoices()
+
+ pos_inv.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
+
+ pos_inv3.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
+
+ pos_inv_cn.load_from_db()
+ self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
+ self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
+
+ frappe.set_user("Administrator")
+ frappe.db.sql("delete from `tabPOS Profile`")
+ frappe.db.sql("delete from `tabPOS Invoice`")
+
+
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/pos_invoice_reference/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/pos_invoice_reference/__init__.py
diff --git a/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json b/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json
new file mode 100644
index 0000000..205c4ed
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json
@@ -0,0 +1,65 @@
+{
+ "actions": [],
+ "creation": "2020-01-28 11:54:47.149392",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "pos_invoice",
+ "posting_date",
+ "column_break_3",
+ "customer",
+ "grand_total"
+ ],
+ "fields": [
+ {
+ "fieldname": "pos_invoice",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "POS Invoice",
+ "options": "POS Invoice",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "pos_invoice.customer",
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "label": "Customer",
+ "options": "Customer",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fetch_from": "pos_invoice.posting_date",
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Date",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "pos_invoice.grand_total",
+ "fieldname": "grand_total",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Amount",
+ "reqd": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-05-29 15:08:42.194979",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Invoice Reference",
+ "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/accounts/doctype/pos_invoice_reference/pos_invoice_reference.py b/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.py
new file mode 100644
index 0000000..4c45265
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class POSInvoiceReference(Document):
+ pass
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/pos_opening_entry/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/pos_opening_entry/__init__.py
diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.js b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.js
new file mode 100644
index 0000000..372e756
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.js
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('POS Opening Entry', {
+ setup(frm) {
+ if (frm.doc.docstatus == 0) {
+ frm.trigger('set_posting_date_read_only');
+ frm.set_value('period_start_date', frappe.datetime.now_datetime());
+ frm.set_value('user', frappe.session.user);
+ }
+
+ frm.set_query("user", function(doc) {
+ return {
+ query: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_cashiers",
+ filters: { 'parent': doc.pos_profile }
+ };
+ });
+ },
+
+ refresh(frm) {
+ // set default posting date / time
+ if(frm.doc.docstatus == 0) {
+ if(!frm.doc.posting_date) {
+ frm.set_value('posting_date', frappe.datetime.nowdate());
+ }
+ frm.trigger('set_posting_date_read_only');
+ }
+ },
+
+ set_posting_date_read_only(frm) {
+ if(frm.doc.docstatus == 0 && frm.doc.set_posting_date) {
+ frm.set_df_property('posting_date', 'read_only', 0);
+ } else {
+ frm.set_df_property('posting_date', 'read_only', 1);
+ }
+ },
+
+ set_posting_date(frm) {
+ frm.trigger('set_posting_date_read_only');
+ },
+
+ pos_profile: (frm) => {
+ if (frm.doc.pos_profile) {
+ frappe.db.get_doc("POS Profile", frm.doc.pos_profile)
+ .then(({ payments }) => {
+ if (payments.length) {
+ frm.doc.balance_details = [];
+ payments.forEach(({ mode_of_payment }) => {
+ frm.add_child("balance_details", { mode_of_payment });
+ })
+ frm.refresh_field("balance_details");
+ }
+ });
+ }
+ }
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.json b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.json
new file mode 100644
index 0000000..de729ce
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.json
@@ -0,0 +1,185 @@
+{
+ "actions": [],
+ "autoname": "POS-OPE-.YYYY.-.#####",
+ "creation": "2020-03-05 16:58:53.083708",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "period_start_date",
+ "period_end_date",
+ "status",
+ "column_break_3",
+ "posting_date",
+ "set_posting_date",
+ "section_break_5",
+ "company",
+ "pos_profile",
+ "pos_closing_entry",
+ "column_break_7",
+ "user",
+ "opening_balance_details_section",
+ "balance_details",
+ "section_break_9",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "period_start_date",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "Period Start Date",
+ "reqd": 1
+ },
+ {
+ "fieldname": "period_end_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Period End Date",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "Today",
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Posting Date",
+ "reqd": 1
+ },
+ {
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "fieldname": "pos_profile",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "POS Profile",
+ "options": "POS Profile",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "user",
+ "fieldtype": "Link",
+ "label": "Cashier",
+ "options": "User",
+ "reqd": 1
+ },
+ {
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break",
+ "read_only": 1
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "POS Opening Entry",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "set_posting_date",
+ "fieldtype": "Check",
+ "label": "Set Posting Date"
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Status",
+ "options": "Draft\nOpen\nClosed\nCancelled",
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "pos_closing_entry",
+ "fieldtype": "Data",
+ "label": "POS Closing Entry",
+ "read_only": 1
+ },
+ {
+ "fieldname": "opening_balance_details_section",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "balance_details",
+ "fieldtype": "Table",
+ "label": "Opening Balance Details",
+ "options": "POS Opening Entry Detail",
+ "reqd": 1
+ }
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-05-29 15:08:40.955310",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Opening Entry",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "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
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Administrator",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
new file mode 100644
index 0000000..15f23b6
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import cint
+from frappe.model.document import Document
+from erpnext.controllers.status_updater import StatusUpdater
+
+class POSOpeningEntry(StatusUpdater):
+ def validate(self):
+ self.validate_pos_profile_and_cashier()
+ self.set_status()
+
+ def validate_pos_profile_and_cashier(self):
+ if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"):
+ frappe.throw(_("POS Profile {} does not belongs to company {}".format(self.pos_profile, self.company)))
+
+ if not cint(frappe.db.get_value("User", self.user, "enabled")):
+ frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
+
+ def on_submit(self):
+ self.set_status(update=True)
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js
new file mode 100644
index 0000000..6c26ded
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+// License: GNU General Public License v3. See license.txt
+
+// render
+frappe.listview_settings['POS Opening Entry'] = {
+ get_indicator: function(doc) {
+ var status_color = {
+ "Draft": "grey",
+ "Open": "orange",
+ "Closed": "green",
+ "Cancelled": "red"
+
+ };
+ return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
+ }
+};
diff --git a/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py
new file mode 100644
index 0000000..2e36391
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestPOSOpeningEntry(unittest.TestCase):
+ pass
+
+def create_opening_entry(pos_profile, user):
+ entry = frappe.new_doc("POS Opening Entry")
+ entry.pos_profile = pos_profile.name
+ entry.user = user
+ entry.company = pos_profile.company
+ entry.period_start_date = frappe.utils.get_datetime()
+
+ balance_details = [];
+ for d in pos_profile.payments:
+ balance_details.append(frappe._dict({
+ 'mode_of_payment': d.mode_of_payment
+ }))
+
+ entry.set("balance_details", balance_details)
+ entry.submit()
+
+ return entry.as_dict()
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/pos_opening_entry_detail/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/pos_opening_entry_detail/__init__.py
diff --git a/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.json b/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.json
new file mode 100644
index 0000000..c23e3df
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.json
@@ -0,0 +1,42 @@
+{
+ "actions": [],
+ "creation": "2020-04-28 16:44:32.440794",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "mode_of_payment",
+ "opening_amount"
+ ],
+ "fields": [
+ {
+ "fieldname": "mode_of_payment",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Mode of Payment",
+ "options": "Mode of Payment",
+ "reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "opening_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Opening Amount",
+ "options": "company:company_currency",
+ "reqd": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-05-29 15:08:41.949378",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Opening Entry Detail",
+ "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/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.py b/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.py
new file mode 100644
index 0000000..5557062
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class POSOpeningEntryDetail(Document):
+ pass
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/doctype/pos_payment_method/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/doctype/pos_payment_method/__init__.py
diff --git a/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.json b/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.json
new file mode 100644
index 0000000..4d5e1eb
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.json
@@ -0,0 +1,40 @@
+{
+ "actions": [],
+ "creation": "2020-04-30 14:37:08.148707",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "default",
+ "mode_of_payment"
+ ],
+ "fields": [
+ {
+ "default": "0",
+ "depends_on": "eval:parent.doctype == 'POS Profile'",
+ "fieldname": "default",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Default"
+ },
+ {
+ "fieldname": "mode_of_payment",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Mode of Payment",
+ "options": "Mode of Payment",
+ "reqd": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-05-29 15:08:41.704844",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Payment Method",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.py b/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.py
new file mode 100644
index 0000000..8a46d84
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_payment_method/pos_payment_method.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class POSPaymentMethod(Document):
+ pass
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js
index 5e94118..ef431d7 100755
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.js
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js
@@ -28,7 +28,7 @@
frappe.ui.form.on('POS Profile', {
setup: function(frm) {
- frm.set_query("print_format_for_online", function() {
+ frm.set_query("print_format", function() {
return {
filters: [
['Print Format', 'doc_type', '=', 'Sales Invoice'],
@@ -49,12 +49,6 @@
return { filters: { doc_type: "Sales Invoice", print_format_type: "JS"} };
});
- frappe.db.get_value('POS Settings', 'POS Settings', 'use_pos_in_offline_mode', (r) => {
- const is_offline = r && cint(r.use_pos_in_offline_mode)
- frm.toggle_display('offline_pos_section', is_offline);
- frm.toggle_display('print_format_for_online', !is_offline);
- });
-
frm.set_query('company_address', function(doc) {
if(!doc.company) {
frappe.throw(__('Please set Company'));
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json
index fba1bed..454c598 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.json
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_rename": 1,
"autoname": "Prompt",
"creation": "2013-05-24 12:15:51",
@@ -11,17 +12,12 @@
"customer",
"company",
"country",
- "warehouse",
- "campaign",
- "company_address",
"column_break_9",
"update_stock",
"ignore_pricing_rule",
- "allow_delete",
- "allow_user_to_edit_rate",
- "allow_user_to_edit_discount",
- "allow_print_before_pay",
- "display_items_in_stock",
+ "warehouse",
+ "campaign",
+ "company_address",
"section_break_15",
"applicable_for_users",
"section_break_11",
@@ -31,16 +27,11 @@
"column_break_16",
"customer_groups",
"section_break_16",
- "print_format_for_online",
+ "print_format",
"letter_head",
"column_break0",
"tc_name",
"select_print_heading",
- "offline_pos_section",
- "territory",
- "column_break_31",
- "print_format",
- "customer_group",
"section_break_19",
"selling_price_list",
"currency",
@@ -105,15 +96,6 @@
"label": "Country"
},
{
- "depends_on": "update_stock",
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "label": "Warehouse",
- "oldfieldname": "warehouse",
- "oldfieldtype": "Link",
- "options": "Warehouse"
- },
- {
"fieldname": "campaign",
"fieldtype": "Link",
"label": "Campaign",
@@ -130,48 +112,6 @@
"fieldtype": "Column Break"
},
{
- "default": "1",
- "fieldname": "update_stock",
- "fieldtype": "Check",
- "label": "Update Stock"
- },
- {
- "default": "0",
- "fieldname": "ignore_pricing_rule",
- "fieldtype": "Check",
- "label": "Ignore Pricing Rule"
- },
- {
- "default": "0",
- "fieldname": "allow_delete",
- "fieldtype": "Check",
- "label": "Allow Delete"
- },
- {
- "default": "0",
- "fieldname": "allow_user_to_edit_rate",
- "fieldtype": "Check",
- "label": "Allow user to edit Rate"
- },
- {
- "default": "0",
- "fieldname": "allow_user_to_edit_discount",
- "fieldtype": "Check",
- "label": "Allow user to edit Discount"
- },
- {
- "default": "0",
- "fieldname": "allow_print_before_pay",
- "fieldtype": "Check",
- "label": "Allow Print Before Pay"
- },
- {
- "default": "0",
- "fieldname": "display_items_in_stock",
- "fieldtype": "Check",
- "label": "Display Items In Stock"
- },
- {
"fieldname": "section_break_15",
"fieldtype": "Section Break",
"label": "Applicable for Users"
@@ -185,13 +125,13 @@
{
"fieldname": "section_break_11",
"fieldtype": "Section Break",
- "label": "Mode of Payment"
+ "label": "Payment Methods"
},
{
"fieldname": "payments",
"fieldtype": "Table",
- "label": "Sales Invoice Payment",
- "options": "Sales Invoice Payment"
+ "options": "POS Payment Method",
+ "reqd": 1
},
{
"fieldname": "section_break_14",
@@ -221,12 +161,6 @@
"label": "Print Settings"
},
{
- "fieldname": "print_format_for_online",
- "fieldtype": "Link",
- "label": "Print Format for Online",
- "options": "Print Format"
- },
- {
"allow_on_submit": 1,
"fieldname": "letter_head",
"fieldtype": "Link",
@@ -259,39 +193,6 @@
"options": "Print Heading"
},
{
- "fieldname": "offline_pos_section",
- "fieldtype": "Section Break",
- "label": "Offline POS Settings"
- },
- {
- "fieldname": "territory",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Territory",
- "oldfieldname": "territory",
- "oldfieldtype": "Link",
- "options": "Territory",
- "reqd": 1
- },
- {
- "fieldname": "column_break_31",
- "fieldtype": "Column Break"
- },
- {
- "default": "Point of Sale",
- "fieldname": "print_format",
- "fieldtype": "Link",
- "label": "Print Format",
- "options": "Print Format"
- },
- {
- "fieldname": "customer_group",
- "fieldtype": "Link",
- "label": "Customer Group",
- "options": "Customer Group",
- "reqd": 1
- },
- {
"fieldname": "section_break_19",
"fieldtype": "Section Break",
"label": "Accounting"
@@ -381,19 +282,48 @@
"label": "Accounting Dimensions"
},
{
- "fieldname": "dimension_col_break",
- "fieldtype": "Column Break"
- },
- {
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "print_format",
+ "fieldtype": "Link",
+ "label": "Print Format",
+ "options": "Print Format"
+ },
+ {
+ "depends_on": "update_stock",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "label": "Warehouse",
+ "oldfieldname": "warehouse",
+ "oldfieldtype": "Link",
+ "options": "Warehouse",
+ "reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "update_stock",
+ "fieldtype": "Check",
+ "label": "Update Stock"
+ },
+ {
+ "default": "0",
+ "fieldname": "ignore_pricing_rule",
+ "fieldtype": "Check",
+ "label": "Ignore Pricing Rule"
}
],
"icon": "icon-cog",
"idx": 1,
- "modified": "2020-01-24 15:52:03.797701",
+ "links": [],
+ "modified": "2020-06-29 12:20:30.977272",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py
index 4f17e9f..789b4c3 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py
@@ -5,8 +5,6 @@
import frappe
from frappe import msgprint, _
from frappe.utils import cint, now
-from erpnext.accounts.doctype.sales_invoice.pos import get_child_nodes
-from erpnext.accounts.doctype.sales_invoice.sales_invoice import set_account_for_mode_of_payment
from six import iteritems
from frappe.model.document import Document
@@ -16,7 +14,6 @@
self.validate_all_link_fields()
self.validate_duplicate_groups()
self.check_default_payment()
- self.validate_customer_territory_group()
def validate_default_profile(self):
for row in self.applicable_for_users:
@@ -64,19 +61,6 @@
if len(default_mode_of_payment) > 1:
frappe.throw(_("Multiple default mode of payment is not allowed"))
- def validate_customer_territory_group(self):
- if not frappe.db.get_single_value('POS Settings', 'use_pos_in_offline_mode'):
- return
-
- if not self.territory:
- frappe.throw(_("Territory is Required in POS Profile"), title="Mandatory Field")
-
- if not self.customer_group:
- frappe.throw(_("Customer Group is Required in POS Profile"), title="Mandatory Field")
-
- def before_save(self):
- set_account_for_mode_of_payment(self)
-
def on_update(self):
self.set_defaults()
@@ -111,10 +95,17 @@
return list(set(item_groups))
+def get_child_nodes(group_type, root):
+ lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
+ return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
+ lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
+
@frappe.whitelist()
def get_series():
- return frappe.get_meta("Sales Invoice").get_field("naming_series").options or ""
+ return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s"
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
user = frappe.session['user']
company = filters.get('company') or frappe.defaults.get_user_default('company')
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py b/erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py
index e28bf73..2e4632a 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py
@@ -8,7 +8,7 @@
'fieldname': 'pos_profile',
'transactions': [
{
- 'items': ['Sales Invoice', 'POS Closing Voucher']
+ 'items': ['Sales Invoice', 'POS Closing Entry', 'POS Opening Entry']
}
]
}
diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
index 64d347d..8a4050c 100644
--- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
@@ -6,7 +6,7 @@
import frappe
import unittest
from erpnext.stock.get_item_details import get_pos_profile
-from erpnext.accounts.doctype.sales_invoice.pos import get_items_list, get_customers_list
+from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes
class TestPOSProfile(unittest.TestCase):
def test_pos_profile(self):
@@ -29,6 +29,44 @@
frappe.db.sql("delete from `tabPOS Profile`")
+def get_customers_list(pos_profile={}):
+ cond = "1=1"
+ customer_groups = []
+ if pos_profile.get('customer_groups'):
+ # Get customers based on the customer groups defined in the POS profile
+ for d in pos_profile.get('customer_groups'):
+ customer_groups.extend([d.get('name') for d in get_child_nodes('Customer Group', d.get('customer_group'))])
+ cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups)))
+
+ return frappe.db.sql(""" select name, customer_name, customer_group,
+ territory, customer_pos_id from tabCustomer where disabled = 0
+ and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {}
+
+def get_items_list(pos_profile, company):
+ cond = ""
+ args_list = []
+ if pos_profile.get('item_groups'):
+ # Get items based on the item groups defined in the POS profile
+ for d in pos_profile.get('item_groups'):
+ args_list.extend([d.name for d in get_child_nodes('Item Group', d.item_group)])
+ if args_list:
+ cond = "and i.item_group in (%s)" % (', '.join(['%s'] * len(args_list)))
+
+ return frappe.db.sql("""
+ select
+ i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no,
+ i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image,
+ id.expense_account, id.selling_cost_center, id.default_warehouse,
+ i.sales_uom, c.conversion_factor
+ from
+ `tabItem` i
+ left join `tabItem Default` id on id.parent = i.name and id.company = %s
+ left join `tabUOM Conversion Detail` c on i.name = c.parent and i.sales_uom = c.uom
+ where
+ i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 and i.is_fixed_asset = 0
+ {cond}
+ """.format(cond=cond), tuple([company] + args_list), as_dict=1)
+
def make_pos_profile(**args):
frappe.db.sql("delete from `tabPOS Profile`")
@@ -50,6 +88,12 @@
"write_off_account": args.write_off_account or "_Test Write Off - _TC",
"write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC"
})
+
+ payments = [{
+ 'mode_of_payment': 'Cash',
+ 'default': 1
+ }]
+ pos_profile.set("payments", payments)
if not frappe.db.exists("POS Profile", args.name or "_Test POS Profile"):
pos_profile.insert()
diff --git a/erpnext/accounts/doctype/pos_profile_user/pos_profile_user.json b/erpnext/accounts/doctype/pos_profile_user/pos_profile_user.json
index 59a673e..c8f3f5e 100644
--- a/erpnext/accounts/doctype/pos_profile_user/pos_profile_user.json
+++ b/erpnext/accounts/doctype/pos_profile_user/pos_profile_user.json
@@ -26,7 +26,7 @@
],
"istable": 1,
"links": [],
- "modified": "2020-05-01 09:46:47.599173",
+ "modified": "2020-05-13 23:57:33.627305",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile User",
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js
index f5b681b..504941d 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.js
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js
@@ -6,27 +6,19 @@
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);
+ 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;
+ }
});
- } else {
- frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""];
- }
+
+ frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields);
+ });
}
});
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json
index 1d55880..3539588 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.json
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json
@@ -5,24 +5,11 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "use_pos_in_offline_mode",
- "section_break_2",
- "fields"
+ "invoice_fields"
],
"fields": [
{
- "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",
+ "fieldname": "invoice_fields",
"fieldtype": "Table",
"label": "POS Field",
"options": "POS Field"
@@ -30,7 +17,7 @@
],
"issingle": 1,
"links": [],
- "modified": "2019-12-26 11:50:47.122997",
+ "modified": "2020-06-01 15:46:41.478928",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Settings",
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index ead300e..cff7d5b 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -276,7 +276,7 @@
item_details.has_pricing_rule = 1
- item_details.pricing_rules = ','.join([d.pricing_rule for d in rules])
+ item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
if not doc: return item_details
@@ -366,7 +366,7 @@
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items
- for d in pricing_rules.split(','):
+ for d in json.loads(pricing_rules):
if not d or not frappe.db.exists("Pricing Rule", d): continue
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
@@ -432,14 +432,15 @@
return doc
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
items = [filters.get('value')]
if filters.get('apply_on') != 'Item Code':
field = frappe.scrub(filters.get('apply_on'))
+ items = [d.name for d in frappe.db.get_all("Item", filters={field: filters.get('value')})]
- items = frappe.db.sql_list("""select name
- from `tabItem` where {0} = %s""".format(field), filters.get('value'))
-
- return frappe.get_all('UOM Conversion Detail',
- filters = {'parent': ('in', items), 'uom': ("like", "{0}%".format(txt))},
- fields = ["distinct uom"], as_list=1)
\ No newline at end of file
+ return frappe.get_all('UOM Conversion Detail', filters={
+ 'parent': ('in', items),
+ 'uom': ("like", "{0}%".format(txt))
+ }, fields = ["distinct uom"], as_list=1)
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 53115f9..3fd316f 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -319,7 +319,9 @@
filtered_rules = []
for field in field_set:
if args.get(field):
- filtered_rules = filter(lambda x: x[field]==args[field], pricing_rules)
+ # filter function always returns a filter object even if empty
+ # list conversion is necessary to check for an empty result
+ filtered_rules = list(filter(lambda x: x.get(field)==args.get(field), pricing_rules))
if filtered_rules: break
return filtered_rules or pricing_rules
@@ -446,7 +448,7 @@
doc.set_missing_values()
def get_applied_pricing_rules(item_row):
- return (item_row.get("pricing_rules").split(',')
+ return (json.loads(item_row.get("pricing_rules"))
if item_row.get("pricing_rules") else [])
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 829c34d..2e91c8e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -26,6 +26,7 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
+ "project",
"supplier_invoice_details",
"bill_no",
"column_break_15",
@@ -170,9 +171,7 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "naming_series",
@@ -184,9 +183,7 @@
"options": "ACC-PINV-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "supplier",
@@ -198,9 +195,7 @@
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
- "search_index": 1,
- "show_days": 1,
- "show_seconds": 1
+ "search_index": 1
},
{
"bold": 1,
@@ -212,9 +207,7 @@
"label": "Supplier Name",
"oldfieldname": "supplier_name",
"oldfieldtype": "Data",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fetch_from": "supplier.tax_id",
@@ -222,27 +215,21 @@
"fieldtype": "Read Only",
"label": "Tax Id",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "due_date",
"fieldtype": "Date",
"label": "Due Date",
"oldfieldname": "due_date",
- "oldfieldtype": "Date",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Date"
},
{
"default": "0",
"fieldname": "is_paid",
"fieldtype": "Check",
"label": "Is Paid",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"default": "0",
@@ -250,25 +237,19 @@
"fieldtype": "Check",
"label": "Is Return (Debit Note)",
"no_copy": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"default": "0",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply Tax Withholding Amount",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -278,17 +259,13 @@
"label": "Company",
"options": "Company",
"print_hide": 1,
- "remember_last_selected_value": 1,
- "show_days": 1,
- "show_seconds": 1
+ "remember_last_selected_value": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
- "options": "Cost Center",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Cost Center"
},
{
"default": "Today",
@@ -300,9 +277,7 @@
"oldfieldtype": "Date",
"print_hide": 1,
"reqd": 1,
- "search_index": 1,
- "show_days": 1,
- "show_seconds": 1
+ "search_index": 1
},
{
"fieldname": "posting_time",
@@ -311,8 +286,6 @@
"no_copy": 1,
"print_hide": 1,
"print_width": "100px",
- "show_days": 1,
- "show_seconds": 1,
"width": "100px"
},
{
@@ -321,9 +294,7 @@
"fieldname": "set_posting_time",
"fieldtype": "Check",
"label": "Edit Posting Date and Time",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "amended_from",
@@ -335,58 +306,44 @@
"oldfieldtype": "Link",
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.on_hold",
"fieldname": "sb_14",
"fieldtype": "Section Break",
- "label": "Hold Invoice",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Hold Invoice"
},
{
"default": "0",
"fieldname": "on_hold",
"fieldtype": "Check",
- "label": "Hold Invoice",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Hold Invoice"
},
{
"depends_on": "eval:doc.on_hold",
"description": "Once set, this invoice will be on hold till the set date",
"fieldname": "release_date",
"fieldtype": "Date",
- "label": "Release Date",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Release Date"
},
{
"fieldname": "cb_17",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.on_hold",
"fieldname": "hold_comment",
"fieldtype": "Small Text",
- "label": "Reason For Putting On Hold",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Reason For Putting On Hold"
},
{
"collapsible": 1,
"collapsible_depends_on": "bill_no",
"fieldname": "supplier_invoice_details",
"fieldtype": "Section Break",
- "label": "Supplier Invoice Details",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Supplier Invoice Details"
},
{
"fieldname": "bill_no",
@@ -394,15 +351,11 @@
"label": "Supplier Invoice No",
"oldfieldname": "bill_no",
"oldfieldtype": "Data",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break_15",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "bill_date",
@@ -410,17 +363,13 @@
"label": "Supplier Invoice Date",
"oldfieldname": "bill_date",
"oldfieldtype": "Date",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"depends_on": "return_against",
"fieldname": "returns",
"fieldtype": "Section Break",
- "label": "Returns",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Returns"
},
{
"depends_on": "return_against",
@@ -430,34 +379,26 @@
"no_copy": 1,
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Address and Contact"
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "contact_person",
@@ -465,67 +406,51 @@
"in_global_search": 1,
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"label": "Contact",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "contact_email",
"fieldtype": "Small Text",
"label": "Contact Email",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-tag"
},
{
"fieldname": "currency",
@@ -534,9 +459,7 @@
"oldfieldname": "currency",
"oldfieldtype": "Select",
"options": "Currency",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "conversion_rate",
@@ -545,24 +468,18 @@
"oldfieldname": "conversion_rate",
"oldfieldtype": "Currency",
"precision": "9",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break2",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "buying_price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "price_list_currency",
@@ -570,18 +487,14 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"default": "0",
@@ -590,15 +503,11 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"depends_on": "update_stock",
@@ -606,9 +515,7 @@
"fieldtype": "Link",
"label": "Set Accepted Warehouse",
"options": "Warehouse",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"depends_on": "update_stock",
@@ -618,15 +525,11 @@
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"default": "No",
@@ -634,9 +537,7 @@
"fieldtype": "Select",
"label": "Raw Materials Supplied",
"options": "No\nYes",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
@@ -647,33 +548,25 @@
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
- "show_days": 1,
- "show_seconds": 1,
"width": "50px"
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-shopping-cart"
},
{
"default": "0",
"fieldname": "update_stock",
"fieldtype": "Check",
"label": "Update Stock",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Scan Barcode"
},
{
"allow_bulk_edit": 1,
@@ -683,56 +576,42 @@
"oldfieldname": "entries",
"oldfieldtype": "Table",
"options": "Purchase Invoice Item",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
- "label": "Pricing Rules",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Pricing Rules"
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible_depends_on": "supplied_items",
"fieldname": "raw_materials_supplied",
"fieldtype": "Section Break",
- "label": "Raw Materials Supplied",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Raw Materials Supplied"
},
{
"fieldname": "supplied_items",
"fieldtype": "Table",
"label": "Supplied Items",
"options": "Purchase Receipt Item Supplied",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "section_break_26",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_total",
@@ -740,9 +619,7 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_net_total",
@@ -752,24 +629,18 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_28",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "net_total",
@@ -779,56 +650,42 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-money"
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break_49",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
"options": "Shipping Rule",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "section_break_51",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "taxes_and_charges",
@@ -837,9 +694,7 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "taxes",
@@ -847,17 +702,13 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Purchase Taxes and Charges"
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Tax Breakup"
},
{
"fieldname": "other_charges_calculation",
@@ -866,17 +717,13 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-money"
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -886,9 +733,7 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -898,9 +743,7 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -910,15 +753,11 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_40",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "taxes_and_charges_added",
@@ -928,9 +767,7 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -940,9 +777,7 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -950,18 +785,14 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "section_break_44",
"fieldtype": "Section Break",
- "label": "Additional Discount",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Additional Discount"
},
{
"default": "Grand Total",
@@ -969,9 +800,7 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "base_discount_amount",
@@ -979,38 +808,28 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_46",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "section_break_49",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "base_grand_total",
@@ -1020,9 +839,7 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_rounding_adjustment",
@@ -1031,9 +848,7 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -1043,28 +858,23 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_in_words",
"fieldtype": "Data",
"label": "In Words (Company Currency)",
+ "length": 240,
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break8",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_hide": 1,
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -1075,9 +885,7 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "rounding_adjustment",
@@ -1086,9 +894,7 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -1098,20 +904,17 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "in_words",
"fieldtype": "Data",
"label": "In Words",
+ "length": 240,
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "total_advance",
@@ -1122,9 +925,7 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "outstanding_amount",
@@ -1135,18 +936,14 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"default": "0",
"depends_on": "grand_total",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Disable Rounded Total"
},
{
"collapsible": 1,
@@ -1154,40 +951,32 @@
"depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)",
"fieldname": "payments_section",
"fieldtype": "Section Break",
- "label": "Payments",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Payments"
},
{
"fieldname": "mode_of_payment",
"fieldtype": "Link",
"label": "Mode of Payment",
"options": "Mode of Payment",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "cash_bank_account",
"fieldtype": "Link",
"label": "Cash/Bank Account",
- "options": "Account",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Account"
},
{
"fieldname": "clearance_date",
"fieldtype": "Date",
- "hidden": 1,
"label": "Clearance Date",
- "show_days": 1,
- "show_seconds": 1
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
},
{
"fieldname": "col_br_payments",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"depends_on": "is_paid",
@@ -1196,9 +985,7 @@
"label": "Paid Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "base_paid_amount",
@@ -1207,9 +994,7 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
@@ -1217,9 +1002,7 @@
"depends_on": "grand_total",
"fieldname": "write_off",
"fieldtype": "Section Break",
- "label": "Write Off",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Write Off"
},
{
"fieldname": "write_off_amount",
@@ -1227,9 +1010,7 @@
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "base_write_off_amount",
@@ -1238,15 +1019,11 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_61",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1254,9 +1031,7 @@
"fieldtype": "Link",
"label": "Write Off Account",
"options": "Account",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1264,9 +1039,7 @@
"fieldtype": "Link",
"label": "Write Off Cost Center",
"options": "Cost Center",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"collapsible": 1,
@@ -1276,17 +1049,13 @@
"label": "Advance Payments",
"oldfieldtype": "Section Break",
"options": "fa fa-money",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"default": "0",
"fieldname": "allocate_advances_automatically",
"fieldtype": "Check",
- "label": "Set Advances and Allocate (FIFO)",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Set Advances and Allocate (FIFO)"
},
{
"depends_on": "eval:!doc.allocate_advances_automatically",
@@ -1294,9 +1063,7 @@
"fieldtype": "Button",
"label": "Get Advances Paid",
"oldfieldtype": "Button",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "advances",
@@ -1306,26 +1073,20 @@
"oldfieldname": "advance_allocation_details",
"oldfieldtype": "Table",
"options": "Purchase Invoice Advance",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:(!doc.is_return)",
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
- "label": "Payment Terms",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Payment Terms"
},
{
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"label": "Payment Terms Template",
- "options": "Payment Terms Template",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Payment Terms Template"
},
{
"fieldname": "payment_schedule",
@@ -1333,9 +1094,7 @@
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"collapsible": 1,
@@ -1343,33 +1102,25 @@
"fieldname": "terms_section_break",
"fieldtype": "Section Break",
"label": "Terms and Conditions",
- "options": "fa fa-legal",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-legal"
},
{
"fieldname": "tc_name",
"fieldtype": "Link",
"label": "Terms",
"options": "Terms and Conditions",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
- "label": "Terms and Conditions1",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Terms and Conditions1"
},
{
"collapsible": 1,
"fieldname": "printing_settings",
"fieldtype": "Section Break",
- "label": "Printing Settings",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Printing Settings"
},
{
"allow_on_submit": 1,
@@ -1377,9 +1128,7 @@
"fieldtype": "Link",
"label": "Letter Head",
"options": "Letter Head",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"allow_on_submit": 1,
@@ -1387,15 +1136,11 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break_112",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
@@ -1407,18 +1152,14 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "report_hide": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
@@ -1427,9 +1168,7 @@
"label": "More Information",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "credit_to",
@@ -1440,9 +1179,7 @@
"options": "Account",
"print_hide": 1,
"reqd": 1,
- "search_index": 1,
- "show_days": 1,
- "show_seconds": 1
+ "search_index": 1
},
{
"fieldname": "party_account_currency",
@@ -1452,9 +1189,7 @@
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"default": "No",
@@ -1464,9 +1199,7 @@
"oldfieldname": "is_opening",
"oldfieldtype": "Select",
"options": "No\nYes",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "against_expense_account",
@@ -1476,15 +1209,11 @@
"no_copy": 1,
"oldfieldname": "against_expense_account",
"oldfieldtype": "Small Text",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break_63",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"default": "Draft",
@@ -1493,18 +1222,14 @@
"in_standard_filter": 1,
"label": "Status",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "inter_company_invoice_reference",
"fieldtype": "Link",
"label": "Inter Company Invoice Reference",
"options": "Sales Invoice",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "remarks",
@@ -1513,18 +1238,14 @@
"no_copy": 1,
"oldfieldname": "remarks",
"oldfieldtype": "Text",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"allow_on_submit": 1,
@@ -1533,9 +1254,7 @@
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"allow_on_submit": 1,
@@ -1544,15 +1263,11 @@
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break_114",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "auto_repeat",
@@ -1561,32 +1276,24 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
- "label": "Update Auto Repeat Reference",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Update Auto Repeat Reference"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
- "label": "Accounting Dimensions ",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Accounting Dimensions "
},
{
"fieldname": "dimension_col_break",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"default": "0",
@@ -1594,9 +1301,7 @@
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "tax_withholding_category",
@@ -1604,32 +1309,32 @@
"hidden": 1,
"label": "Tax Withholding Category",
"options": "Tax Withholding Category",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "billing_address",
"fieldtype": "Link",
"label": "Select Billing Address",
- "options": "Address",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Address"
},
{
"fieldname": "billing_address_display",
"fieldtype": "Small Text",
"label": "Billing Address",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2020-06-13 22:26:30.800199",
+ "modified": "2020-08-03 12:46:01.411074",
"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 3cd57d4..7b1062f 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -438,6 +438,8 @@
self.make_tax_gl_entries(gl_entries)
+ gl_entries = make_regional_gl_entries(gl_entries, self)
+
gl_entries = merge_similar_entries(gl_entries)
self.make_payment_gl_entries(gl_entries)
@@ -476,6 +478,7 @@
if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
+ "project": self.project,
"cost_center": self.cost_center
}, self.party_account_currency, item=self)
)
@@ -516,6 +519,7 @@
"account": warehouse_account[item.warehouse]['account'],
"against": warehouse_account[item.from_warehouse]["account"],
"cost_center": item.cost_center,
+ "project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": warehouse_debit_amount,
}, warehouse_account[item.warehouse]["account_currency"], item=item))
@@ -525,6 +529,7 @@
"account": warehouse_account[item.from_warehouse]['account'],
"against": warehouse_account[item.warehouse]["account"],
"cost_center": item.cost_center,
+ "project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
}, warehouse_account[item.from_warehouse]["account_currency"], item=item))
@@ -548,7 +553,7 @@
"debit": warehouse_debit_amount,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
- "project": item.project
+ "project": item.project or self.project
}, account_currency, item=item)
)
@@ -561,7 +566,7 @@
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(amount),
- "project": item.project
+ "project": item.project or self.project
}, item=item))
# sub-contracting warehouse
@@ -574,6 +579,7 @@
"account": supplier_warehouse_account,
"against": item.expense_account,
"cost_center": item.cost_center,
+ "project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.rm_supp_cost)
}, warehouse_account[self.supplier_warehouse]["account_currency"], item=item))
@@ -606,7 +612,7 @@
"against": self.supplier,
"debit": amount,
"cost_center": item.cost_center,
- "project": item.project
+ "project": item.project or self.project
}, account_currency, item=item))
# If asset is bought through this document and not linked to PR
@@ -619,7 +625,7 @@
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.landed_cost_voucher_amount),
- "project": item.project
+ "project": item.project or self.project
}, item=item))
gl_entries.append(self.get_gl_dict({
@@ -628,7 +634,7 @@
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(item.landed_cost_voucher_amount),
- "project": item.project
+ "project": item.project or self.project
}, item=item))
# update gross amount of asset bought through this document
@@ -654,7 +660,8 @@
"against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"remarks": self.remarks or "Accounting Entry for Stock",
- "cost_center": self.cost_center
+ "cost_center": self.cost_center,
+ "project": item.project or self.project
}, item=item)
)
@@ -683,7 +690,8 @@
"debit": base_asset_amount,
"debit_in_account_currency": (base_asset_amount
if arbnb_currency == self.company_currency else asset_amount),
- "cost_center": item.cost_center
+ "cost_center": item.cost_center,
+ "project": item.project or self.project
}, item=item))
if item.item_tax_amount:
@@ -693,6 +701,7 @@
"against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"cost_center": item.cost_center,
+ "project": item.project or self.project,
"credit": item.item_tax_amount,
"credit_in_account_currency": (item.item_tax_amount
if asset_eiiav_currency == self.company_currency else
@@ -709,7 +718,8 @@
"debit": base_asset_amount,
"debit_in_account_currency": (base_asset_amount
if cwip_account_currency == self.company_currency else asset_amount),
- "cost_center": self.cost_center
+ "cost_center": self.cost_center,
+ "project": item.project or self.project
}, item=item))
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
@@ -720,6 +730,7 @@
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"cost_center": item.cost_center,
"credit": item.item_tax_amount,
+ "project": item.project or self.project,
"credit_in_account_currency": (item.item_tax_amount
if asset_eiiav_currency == self.company_currency else
item.item_tax_amount / self.conversion_rate)
@@ -735,7 +746,7 @@
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.landed_cost_voucher_amount),
- "project": item.project
+ "project": item.project or self.project
}, item=item))
gl_entries.append(self.get_gl_dict({
@@ -744,7 +755,7 @@
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(item.landed_cost_voucher_amount),
- "project": item.project
+ "project": item.project or self.project
}, item=item))
# update gross amount of assets bought through this document
@@ -779,7 +790,7 @@
"debit": stock_adjustment_amt,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
- "project": item.project
+ "project": item.project or self.project
}, account_currency, item=item)
)
@@ -871,7 +882,8 @@
if self.party_account_currency==self.company_currency else self.paid_amount,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
- "cost_center": self.cost_center
+ "cost_center": self.cost_center,
+ "project": self.project
}, self.party_account_currency, item=self)
)
@@ -903,7 +915,8 @@
if self.party_account_currency==self.company_currency else self.write_off_amount,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
- "cost_center": self.cost_center
+ "cost_center": self.cost_center,
+ "project": self.project
}, self.party_account_currency, item=self)
)
gl_entries.append(
@@ -1097,6 +1110,10 @@
})
return list_context
+@erpnext.allow_regional
+def make_regional_gl_entries(gl_entries, doc):
+ return gl_entries
+
@frappe.whitelist()
def make_debit_note(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index b5955ca..9a666bf 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -14,6 +14,7 @@
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
+from erpnext.projects.doctype.project.test_project import make_project
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
from erpnext.stock.doctype.item.test_item import create_item
@@ -435,6 +436,8 @@
)
def test_total_purchase_cost_for_project(self):
+ make_project({'project_name':'_Test Project'})
+
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""")
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
@@ -808,11 +811,8 @@
pi_doc = frappe.get_doc('Purchase Invoice', pi.name)
self.assertEqual(pi_doc.outstanding_amount, 0)
- def test_purchase_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ def test_purchase_invoice_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
@@ -838,13 +838,7 @@
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
-
- def test_purchase_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
+ def test_purchase_invoice_without_cost_center(self):
cost_center = "_Test Cost Center - _TC"
pi = make_purchase_invoice(credit_to="Creditors - _TC")
@@ -867,6 +861,43 @@
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+ def test_purchase_invoice_with_project_link(self):
+ project = make_project({
+ 'project_name': 'Purchase Invoice Project',
+ 'project_template_name': 'Test Project Template',
+ 'start_date': '2020-01-01'
+ })
+ item_project = make_project({
+ 'project_name': 'Purchase Invoice Item Project',
+ 'project_template_name': 'Test Project Template',
+ 'start_date': '2019-06-01'
+ })
+
+ pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
+ pi.items[0].project = item_project.project_name
+ pi.project = project.project_name
+
+ pi.submit()
+
+ expected_values = {
+ "Creditors - _TC": {
+ "project": project.project_name
+ },
+ "_Test Account Cost for Goods Sold - _TC": {
+ "project": item_project.project_name
+ }
+ }
+
+ gl_entries = frappe.db.sql("""select account, cost_center, project, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
+ order by account asc""", pi.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(expected_values[gle.account]["project"], gle.project)
+
def test_deferred_expense_via_journal_entry(self):
deferred_account = create_account(account_name="Deferred Expense",
parent_account="Current Assets - _TC", company="_Test Company")
diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py
deleted file mode 100755
index c49ac29..0000000
--- a/erpnext/accounts/doctype/sales_invoice/pos.py
+++ /dev/null
@@ -1,626 +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 json
-
-import frappe
-from erpnext.accounts.party import get_party_account_currency
-from erpnext.controllers.accounts_controller import get_taxes_and_charges
-from erpnext.setup.utils import get_exchange_rate
-from erpnext.stock.get_item_details import get_pos_profile
-from frappe import _
-from frappe.core.doctype.communication.email import make
-from frappe.utils import nowdate, cint
-
-from six import string_types, iteritems
-
-
-@frappe.whitelist()
-def get_pos_data():
- doc = frappe.new_doc('Sales Invoice')
- doc.is_pos = 1
- pos_profile = get_pos_profile(doc.company) or {}
- if not pos_profile:
- frappe.throw(_("POS Profile is required to use Point-of-Sale"))
-
- if not doc.company:
- doc.company = pos_profile.get('company')
-
- doc.update_stock = pos_profile.get('update_stock')
-
- if pos_profile.get('name'):
- pos_profile = frappe.get_doc('POS Profile', pos_profile.get('name'))
- pos_profile.validate()
-
- company_data = get_company_data(doc.company)
- update_pos_profile_data(doc, pos_profile, company_data)
- update_multi_mode_option(doc, pos_profile)
- default_print_format = pos_profile.get('print_format') or "Point of Sale"
- print_template = frappe.db.get_value('Print Format', default_print_format, 'html')
- items_list = get_items_list(pos_profile, doc.company)
- customers = get_customers_list(pos_profile)
-
- doc.plc_conversion_rate = update_plc_conversion_rate(doc, pos_profile)
-
- return {
- 'doc': doc,
- 'default_customer': pos_profile.get('customer'),
- 'items': items_list,
- 'item_groups': get_item_groups(pos_profile),
- 'customers': customers,
- 'address': get_customers_address(customers),
- 'contacts': get_contacts(customers),
- 'serial_no_data': get_serial_no_data(pos_profile, doc.company),
- 'batch_no_data': get_batch_no_data(),
- 'barcode_data': get_barcode_data(items_list),
- 'tax_data': get_item_tax_data(),
- 'price_list_data': get_price_list_data(doc.selling_price_list, doc.plc_conversion_rate),
- 'customer_wise_price_list': get_customer_wise_price_list(),
- 'bin_data': get_bin_data(pos_profile),
- 'pricing_rules': get_pricing_rule_data(doc),
- 'print_template': print_template,
- 'pos_profile': pos_profile,
- 'meta': get_meta()
- }
-
-def update_plc_conversion_rate(doc, pos_profile):
- conversion_rate = 1.0
-
- price_list_currency = frappe.get_cached_value("Price List", doc.selling_price_list, "currency")
- if pos_profile.get("currency") != price_list_currency:
- conversion_rate = get_exchange_rate(price_list_currency,
- pos_profile.get("currency"), nowdate(), args="for_selling") or 1.0
-
- return conversion_rate
-
-def get_meta():
- doctype_meta = {
- 'customer': frappe.get_meta('Customer'),
- 'invoice': frappe.get_meta('Sales Invoice')
- }
-
- for row in frappe.get_all('DocField', fields=['fieldname', 'options'],
- filters={'parent': 'Sales Invoice', 'fieldtype': 'Table'}):
- doctype_meta[row.fieldname] = frappe.get_meta(row.options)
-
- return doctype_meta
-
-
-def get_company_data(company):
- return frappe.get_all('Company', fields=["*"], filters={'name': company})[0]
-
-
-def update_pos_profile_data(doc, pos_profile, company_data):
- doc.campaign = pos_profile.get('campaign')
- if pos_profile and not pos_profile.get('country'):
- pos_profile.country = company_data.country
-
- doc.write_off_account = pos_profile.get('write_off_account') or \
- company_data.write_off_account
- doc.change_amount_account = pos_profile.get('change_amount_account') or \
- company_data.default_cash_account
- doc.taxes_and_charges = pos_profile.get('taxes_and_charges')
- if doc.taxes_and_charges:
- update_tax_table(doc)
-
- doc.currency = pos_profile.get('currency') or company_data.default_currency
- doc.conversion_rate = 1.0
-
- if doc.currency != company_data.default_currency:
- doc.conversion_rate = get_exchange_rate(doc.currency, company_data.default_currency, doc.posting_date, args="for_selling")
-
- doc.selling_price_list = pos_profile.get('selling_price_list') or \
- frappe.db.get_value('Selling Settings', None, 'selling_price_list')
- doc.naming_series = pos_profile.get('naming_series') or 'SINV-'
- doc.letter_head = pos_profile.get('letter_head') or company_data.default_letter_head
- doc.ignore_pricing_rule = pos_profile.get('ignore_pricing_rule') or 0
- doc.apply_discount_on = pos_profile.get('apply_discount_on') or 'Grand Total'
- doc.customer_group = pos_profile.get('customer_group') or get_root('Customer Group')
- doc.territory = pos_profile.get('territory') or get_root('Territory')
- doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get('tc_name'), 'terms') or doc.terms or ''
- doc.offline_pos_name = ''
-
-
-def get_root(table):
- root = frappe.db.sql(""" select name from `tab%(table)s` having
- min(lft)""" % {'table': table}, as_dict=1)
-
- return root[0].name
-
-
-def update_multi_mode_option(doc, pos_profile):
- from frappe.model import default_fields
-
- if not pos_profile or not pos_profile.get('payments'):
- for payment in get_mode_of_payment(doc):
- payments = doc.append('payments', {})
- payments.mode_of_payment = payment.parent
- payments.account = payment.default_account
- payments.type = payment.type
-
- return
-
- for payment_mode in pos_profile.payments:
- payment_mode = payment_mode.as_dict()
-
- for fieldname in default_fields:
- if fieldname in payment_mode:
- del payment_mode[fieldname]
-
- doc.append('payments', payment_mode)
-
-
-def get_mode_of_payment(doc):
- return frappe.db.sql("""
- select mpa.default_account, mpa.parent, mp.type as type
- from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
- where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
- {'company': doc.company}, as_dict=1)
-
-
-def update_tax_table(doc):
- taxes = get_taxes_and_charges('Sales Taxes and Charges Template', doc.taxes_and_charges)
- for tax in taxes:
- doc.append('taxes', tax)
-
-
-def get_items_list(pos_profile, company):
- cond = ""
- args_list = []
- if pos_profile.get('item_groups'):
- # Get items based on the item groups defined in the POS profile
- for d in pos_profile.get('item_groups'):
- args_list.extend([d.name for d in get_child_nodes('Item Group', d.item_group)])
- if args_list:
- cond = "and i.item_group in (%s)" % (', '.join(['%s'] * len(args_list)))
-
- return frappe.db.sql("""
- select
- i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no,
- i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image,
- id.expense_account, id.selling_cost_center, id.default_warehouse,
- i.sales_uom, c.conversion_factor
- from
- `tabItem` i
- left join `tabItem Default` id on id.parent = i.name and id.company = %s
- left join `tabUOM Conversion Detail` c on i.name = c.parent and i.sales_uom = c.uom
- where
- i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1
- {cond}
- """.format(cond=cond), tuple([company] + args_list), as_dict=1)
-
-
-def get_item_groups(pos_profile):
- item_group_dict = {}
- item_groups = frappe.db.sql("""Select name,
- lft, rgt from `tabItem Group` order by lft""", as_dict=1)
-
- for data in item_groups:
- item_group_dict[data.name] = [data.lft, data.rgt]
- return item_group_dict
-
-
-def get_customers_list(pos_profile={}):
- cond = "1=1"
- customer_groups = []
- if pos_profile.get('customer_groups'):
- # Get customers based on the customer groups defined in the POS profile
- for d in pos_profile.get('customer_groups'):
- customer_groups.extend([d.get('name') for d in get_child_nodes('Customer Group', d.get('customer_group'))])
- cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups)))
-
- return frappe.db.sql(""" select name, customer_name, customer_group,
- territory, customer_pos_id from tabCustomer where disabled = 0
- and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {}
-
-
-def get_customers_address(customers):
- customer_address = {}
- if isinstance(customers, string_types):
- customers = [frappe._dict({'name': customers})]
-
- for data in customers:
- address = frappe.db.sql(""" select name, address_line1, address_line2, city, state,
- email_id, phone, fax, pincode from `tabAddress` where is_primary_address =1 and name in
- (select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
- and parenttype = 'Address')""", data.name, as_dict=1)
- address_data = {}
- if address:
- address_data = address[0]
-
- address_data.update({'full_name': data.customer_name, 'customer_pos_id': data.customer_pos_id})
- customer_address[data.name] = address_data
-
- return customer_address
-
-
-def get_contacts(customers):
- customer_contact = {}
- if isinstance(customers, string_types):
- customers = [frappe._dict({'name': customers})]
-
- for data in customers:
- contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact`
- where is_primary_contact=1 and name in
- (select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
- and parenttype = 'Contact')""", data.name, as_dict=1)
- if contact:
- customer_contact[data.name] = contact[0]
-
- return customer_contact
-
-
-def get_child_nodes(group_type, root):
- lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
- return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
- lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
-
-
-def get_serial_no_data(pos_profile, company):
- # get itemwise serial no data
- # example {'Nokia Lumia 1020': {'SN0001': 'Pune'}}
- # where Nokia Lumia 1020 is item code, SN0001 is serial no and Pune is warehouse
-
- cond = "1=1"
- if pos_profile.get('update_stock') and pos_profile.get('warehouse'):
- cond = "warehouse = %(warehouse)s"
-
- serial_nos = frappe.db.sql("""select name, warehouse, item_code
- from `tabSerial No` where {0} and company = %(company)s """.format(cond),{
- 'company': company, 'warehouse': frappe.db.escape(pos_profile.get('warehouse'))
- }, as_dict=1)
-
- itemwise_serial_no = {}
- for sn in serial_nos:
- if sn.item_code not in itemwise_serial_no:
- itemwise_serial_no.setdefault(sn.item_code, {})
- itemwise_serial_no[sn.item_code][sn.name] = sn.warehouse
-
- return itemwise_serial_no
-
-
-def get_batch_no_data():
- # get itemwise batch no data
- # exmaple: {'LED-GRE': [Batch001, Batch002]}
- # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse
-
- itemwise_batch = {}
- batches = frappe.db.sql("""select name, item from `tabBatch`
- where ifnull(expiry_date, '4000-10-10') >= curdate()""", as_dict=1)
-
- for batch in batches:
- if batch.item not in itemwise_batch:
- itemwise_batch.setdefault(batch.item, [])
- itemwise_batch[batch.item].append(batch.name)
-
- return itemwise_batch
-
-
-def get_barcode_data(items_list):
- # get itemwise batch no data
- # exmaple: {'LED-GRE': [Batch001, Batch002]}
- # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse
-
- itemwise_barcode = {}
- for item in items_list:
- barcodes = frappe.db.sql("""
- select barcode from `tabItem Barcode` where parent = %s
- """, item.item_code, as_dict=1)
-
- for barcode in barcodes:
- if item.item_code not in itemwise_barcode:
- itemwise_barcode.setdefault(item.item_code, [])
- itemwise_barcode[item.item_code].append(barcode.get("barcode"))
-
- return itemwise_barcode
-
-
-def get_item_tax_data():
- # get default tax of an item
- # example: {'Consulting Services': {'Excise 12 - TS': '12.000'}}
-
- itemwise_tax = {}
- taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax Template Detail`""", as_dict=1)
-
- for tax in taxes:
- if tax.parent not in itemwise_tax:
- itemwise_tax.setdefault(tax.parent, {})
- itemwise_tax[tax.parent][tax.tax_type] = tax.tax_rate
-
- return itemwise_tax
-
-
-def get_price_list_data(selling_price_list, conversion_rate):
- itemwise_price_list = {}
- price_lists = frappe.db.sql("""Select ifnull(price_list_rate, 0) as price_list_rate,
- item_code from `tabItem Price` ip where price_list = %(price_list)s""",
- {'price_list': selling_price_list}, as_dict=1)
-
- for item in price_lists:
- itemwise_price_list[item.item_code] = item.price_list_rate * conversion_rate
-
- return itemwise_price_list
-
-def get_customer_wise_price_list():
- customer_wise_price = {}
- customer_price_list_mapping = frappe._dict(frappe.get_all('Customer',fields = ['default_price_list', 'name'], as_list=1))
-
- price_lists = frappe.db.sql(""" Select ifnull(price_list_rate, 0) as price_list_rate,
- item_code, price_list from `tabItem Price` """, as_dict=1)
-
- for item in price_lists:
- if item.price_list and customer_price_list_mapping.get(item.price_list):
-
- customer_wise_price.setdefault(customer_price_list_mapping.get(item.price_list),{}).setdefault(
- item.item_code, item.price_list_rate
- )
-
- return customer_wise_price
-
-def get_bin_data(pos_profile):
- itemwise_bin_data = {}
- filters = { 'actual_qty': ['>', 0] }
- if pos_profile.get('warehouse'):
- filters.update({ 'warehouse': pos_profile.get('warehouse') })
-
- 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:
- itemwise_bin_data.setdefault(bins.item_code, {})
- itemwise_bin_data[bins.item_code][bins.warehouse] = bins.actual_qty
-
- return itemwise_bin_data
-
-
-def get_pricing_rule_data(doc):
- pricing_rules = ""
- if doc.ignore_pricing_rule == 0:
- pricing_rules = frappe.db.sql(""" Select * from `tabPricing Rule` where docstatus < 2
- and ifnull(for_price_list, '') in (%(price_list)s, '') and selling = 1
- and ifnull(company, '') in (%(company)s, '') and disable = 0 and %(date)s
- between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')
- order by priority desc, name desc""",
- {'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1)
- return pricing_rules
-
-
-@frappe.whitelist()
-def make_invoice(pos_profile, doc_list={}, email_queue_list={}, customers_list={}):
- import json
-
- if isinstance(doc_list, string_types):
- doc_list = json.loads(doc_list)
-
- if isinstance(email_queue_list, string_types):
- email_queue_list = json.loads(email_queue_list)
-
- if isinstance(customers_list, string_types):
- customers_list = json.loads(customers_list)
-
- customers_list = make_customer_and_address(customers_list)
- name_list = []
- for docs in doc_list:
- for name, doc in iteritems(docs):
- if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):
- 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)
-
- email_queue = make_email_queue(email_queue_list)
-
- if isinstance(pos_profile, string_types):
- pos_profile = json.loads(pos_profile)
-
- customers = get_customers_list(pos_profile)
- return {
- 'invoice': name_list,
- 'email_queue': email_queue,
- 'customers': customers_list,
- 'synced_customers_list': customers,
- 'synced_address': get_customers_address(customers),
- 'synced_contacts': get_contacts(customers)
- }
-
-
-def validate_records(doc):
- validate_item(doc)
-
-
-def get_customer_id(doc, customer=None):
- cust_id = None
- if doc.get('customer_pos_id'):
- cust_id = frappe.db.get_value('Customer',{'customer_pos_id': doc.get('customer_pos_id')}, 'name')
-
- if not cust_id:
- customer = customer or doc.get('customer')
- if frappe.db.exists('Customer', customer):
- cust_id = customer
- else:
- cust_id = add_customer(doc)
-
- return cust_id
-
-def make_customer_and_address(customers):
- customers_list = []
- for customer, data in iteritems(customers):
- data = json.loads(data)
- cust_id = get_customer_id(data, customer)
- if not cust_id:
- cust_id = add_customer(data)
- else:
- frappe.db.set_value("Customer", cust_id, "customer_name", data.get('full_name'))
-
- make_contact(data, cust_id)
- make_address(data, cust_id)
- customers_list.append(customer)
- frappe.db.commit()
- return customers_list
-
-def add_customer(data):
- customer = data.get('full_name') or data.get('customer')
- if frappe.db.exists("Customer", customer.strip()):
- return customer.strip()
-
- customer_doc = frappe.new_doc('Customer')
- customer_doc.customer_name = data.get('full_name') or data.get('customer')
- customer_doc.customer_pos_id = data.get('customer_pos_id')
- customer_doc.customer_type = 'Company'
- customer_doc.customer_group = get_customer_group(data)
- customer_doc.territory = get_territory(data)
- customer_doc.flags.ignore_mandatory = True
- customer_doc.save(ignore_permissions=True)
- frappe.db.commit()
- return customer_doc.name
-
-def get_territory(data):
- if data.get('territory'):
- return data.get('territory')
-
- return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories')
-
-def get_customer_group(data):
- if data.get('customer_group'):
- return data.get('customer_group')
-
- return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name')
-
-def make_contact(args, customer):
- if args.get('email_id') or args.get('phone'):
- name = frappe.db.get_value('Dynamic Link',
- {'link_doctype': 'Customer', 'link_name': customer, 'parenttype': 'Contact'}, 'parent')
-
- args = {
- 'first_name': args.get('full_name'),
- 'email_id': args.get('email_id'),
- 'phone': args.get('phone')
- }
-
- doc = frappe.new_doc('Contact')
- if name:
- doc = frappe.get_doc('Contact', name)
-
- doc.update(args)
- doc.is_primary_contact = 1
- if not name:
- doc.append('links', {
- 'link_doctype': 'Customer',
- 'link_name': customer
- })
- doc.flags.ignore_mandatory = True
- doc.save(ignore_permissions=True)
-
-def make_address(args, customer):
- if not args.get('address_line1'):
- return
-
- name = args.get('name')
-
- if not name:
- data = get_customers_address(customer)
- name = data[customer].get('name') if data else None
-
- if name:
- address = frappe.get_doc('Address', name)
- else:
- address = frappe.new_doc('Address')
- if args.get('company'):
- address.country = frappe.get_cached_value('Company',
- args.get('company'), 'country')
-
- address.append('links', {
- 'link_doctype': 'Customer',
- 'link_name': customer
- })
-
- address.is_primary_address = 1
- address.is_shipping_address = 1
- address.update(args)
- address.flags.ignore_mandatory = True
- address.save(ignore_permissions=True)
-
-def make_email_queue(email_queue):
- name_list = []
-
- for key, data in iteritems(email_queue):
- 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'),
- sender=sender, attachments=attachments, send_email=True,
- doctype='Sales Invoice', name=name)
- name_list.append(key)
-
- return name_list
-
-def validate_item(doc):
- for item in doc.get('items'):
- if not frappe.db.exists('Item', item.get('item_code')):
- item_doc = frappe.new_doc('Item')
- item_doc.name = item.get('item_code')
- item_doc.item_code = item.get('item_code')
- item_doc.item_name = item.get('item_name')
- item_doc.description = item.get('description')
- item_doc.stock_uom = item.get('stock_uom')
- item_doc.uom = item.get('uom')
- item_doc.item_group = item.get('item_group')
- item_doc.append('item_defaults', {
- "company": doc.get("company"),
- "default_warehouse": item.get('warehouse')
- })
- item_doc.save(ignore_permissions=True)
- frappe.db.commit()
-
-def submit_invoice(si_doc, name, doc, name_list):
- try:
- si_doc.insert()
- si_doc.submit()
- frappe.db.commit()
- name_list.append(name)
- except Exception as e:
- if frappe.message_log:
- frappe.message_log.pop()
- frappe.db.rollback()
- frappe.log_error(frappe.get_traceback())
- name_list = save_invoice(doc, name, name_list)
-
- return name_list
-
-def save_invoice(doc, name, name_list):
- try:
- if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):
- si = frappe.new_doc('Sales Invoice')
- si.update(doc)
- si.set_posting_time = 1
- si.customer = get_customer_id(doc)
- si.due_date = doc.get('posting_date')
- si.flags.ignore_mandatory = True
- si.insert(ignore_permissions=True)
- frappe.db.commit()
- name_list.append(name)
- except Exception:
- frappe.db.rollback()
- frappe.log_error(frappe.get_traceback())
-
- return name_list
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index df0c3d2..9af584e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -96,6 +96,12 @@
cur_frm.add_custom_button(__('Invoice Discounting'), function() {
cur_frm.events.create_invoice_discounting(cur_frm);
}, __('Create'));
+
+ if (doc.due_date < frappe.datetime.get_today()) {
+ cur_frm.add_custom_button(__('Dunning'), function() {
+ cur_frm.events.create_dunning(cur_frm);
+ }, __('Create'));
+ }
}
if (doc.docstatus === 1) {
@@ -276,7 +282,7 @@
"customer": this.frm.doc.customer
},
callback: function(r) {
- if(r.message && r.message.length) {
+ if(r.message && r.message.length > 1) {
select_loyalty_program(me.frm, r.message);
}
}
@@ -824,6 +830,12 @@
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_invoice_discounting",
frm: frm
});
+ },
+ create_dunning: function(frm) {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
+ frm: frm
+ });
}
})
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 63c34ed..4dc81e9 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1,6 +1,7 @@
{
"actions": [],
"allow_import": 1,
+ "allow_workflow": 1,
"autoname": "naming_series:",
"creation": "2013-05-24 19:29:05",
"doctype": "DocType",
@@ -13,6 +14,7 @@
"customer_name",
"tax_id",
"is_pos",
+ "is_consolidated",
"pos_profile",
"offline_pos_name",
"is_return",
@@ -189,6 +191,8 @@
{
"fieldname": "customer_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"options": "fa fa-user"
},
{
@@ -197,6 +201,8 @@
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Title",
"no_copy": 1,
"print_hide": 1
@@ -205,6 +211,8 @@
"bold": 1,
"fieldname": "naming_series",
"fieldtype": "Select",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Series",
"no_copy": 1,
"oldfieldname": "naming_series",
@@ -218,6 +226,8 @@
"bold": 1,
"fieldname": "customer",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_standard_filter": 1,
"label": "Customer",
"oldfieldname": "customer",
@@ -232,6 +242,8 @@
"fetch_from": "customer.customer_name",
"fieldname": "customer_name",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_global_search": 1,
"label": "Customer Name",
"oldfieldname": "customer_name",
@@ -241,6 +253,8 @@
{
"fieldname": "tax_id",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Tax Id",
"print_hide": 1,
"read_only": 1
@@ -248,6 +262,8 @@
{
"fieldname": "project",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_global_search": 1,
"label": "Project",
"oldfieldname": "project_name",
@@ -259,6 +275,8 @@
"default": "0",
"fieldname": "is_pos",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Include Payment (POS)",
"oldfieldname": "is_pos",
"oldfieldtype": "Check",
@@ -268,6 +286,8 @@
"depends_on": "is_pos",
"fieldname": "pos_profile",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "POS Profile",
"options": "POS Profile",
"print_hide": 1
@@ -276,6 +296,8 @@
"fieldname": "offline_pos_name",
"fieldtype": "Data",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Offline POS Name",
"print_hide": 1,
"read_only": 1
@@ -284,6 +306,8 @@
"default": "0",
"fieldname": "is_return",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Is Return (Credit Note)",
"no_copy": 1,
"print_hide": 1
@@ -291,11 +315,15 @@
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Column Break"
},
{
"fieldname": "company",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_standard_filter": 1,
"label": "Company",
"oldfieldname": "company",
@@ -308,6 +336,8 @@
{
"fieldname": "cost_center",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Cost Center",
"options": "Cost Center"
},
@@ -316,6 +346,8 @@
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Date",
"no_copy": 1,
"oldfieldname": "posting_date",
@@ -326,6 +358,8 @@
{
"fieldname": "posting_time",
"fieldtype": "Time",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Posting Time",
"no_copy": 1,
"oldfieldname": "posting_time",
@@ -337,12 +371,16 @@
"depends_on": "eval:doc.docstatus==0",
"fieldname": "set_posting_time",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Edit Posting Date and Time",
"print_hide": 1
},
{
"fieldname": "due_date",
"fieldtype": "Date",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Payment Due Date",
"no_copy": 1,
"oldfieldname": "due_date",
@@ -351,6 +389,8 @@
{
"fieldname": "amended_from",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"ignore_user_permissions": 1,
"label": "Amended From",
"no_copy": 1,
@@ -364,12 +404,16 @@
"depends_on": "return_against",
"fieldname": "returns",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Returns"
},
{
"depends_on": "return_against",
"fieldname": "return_against",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Return Against Sales Invoice",
"no_copy": 1,
"options": "Sales Invoice",
@@ -379,13 +423,17 @@
},
{
"fieldname": "column_break_21",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"default": "0",
"depends_on": "eval: doc.is_return && doc.return_against",
"fieldname": "update_billed_amount_in_sales_order",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Update Billed Amount in Sales Order"
},
{
@@ -393,35 +441,47 @@
"collapsible_depends_on": "po_no",
"fieldname": "customer_po_details",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Customer PO Details"
},
{
"allow_on_submit": 1,
"fieldname": "po_no",
"fieldtype": "Small Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Customer's Purchase Order",
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "column_break_23",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "po_date",
"fieldtype": "Date",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Customer's Purchase Order Date"
},
{
"collapsible": 1,
"fieldname": "address_and_contact",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Address and Contact"
},
{
"fieldname": "customer_address",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Customer Address",
"options": "Address",
"print_hide": 1
@@ -429,12 +489,16 @@
{
"fieldname": "address_display",
"fieldtype": "Small Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Address",
"read_only": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_global_search": 1,
"label": "Contact Person",
"options": "Contact",
@@ -443,6 +507,8 @@
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Contact",
"read_only": 1
},
@@ -450,6 +516,8 @@
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Mobile No",
"read_only": 1
},
@@ -457,6 +525,8 @@
"fieldname": "contact_email",
"fieldtype": "Data",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
@@ -465,17 +535,23 @@
{
"fieldname": "territory",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Territory",
"options": "Territory",
"print_hide": 1
},
{
"fieldname": "col_break4",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "shipping_address_name",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Shipping Address Name",
"options": "Address",
"print_hide": 1
@@ -483,6 +559,8 @@
{
"fieldname": "shipping_address",
"fieldtype": "Small Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Shipping Address",
"print_hide": 1,
"read_only": 1
@@ -490,6 +568,8 @@
{
"fieldname": "company_address",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Company Address Name",
"options": "Address",
"print_hide": 1
@@ -498,6 +578,8 @@
"fieldname": "company_address_display",
"fieldtype": "Small Text",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Company Address",
"print_hide": 1,
"read_only": 1
@@ -507,11 +589,15 @@
"depends_on": "customer",
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Currency and Price List"
},
{
"fieldname": "currency",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Currency",
"oldfieldname": "currency",
"oldfieldtype": "Select",
@@ -523,6 +609,8 @@
"description": "Rate at which Customer Currency is converted to customer's base currency",
"fieldname": "conversion_rate",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Exchange Rate",
"oldfieldname": "conversion_rate",
"oldfieldtype": "Currency",
@@ -533,11 +621,15 @@
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"width": "50%"
},
{
"fieldname": "selling_price_list",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Price List",
"oldfieldname": "price_list_name",
"oldfieldtype": "Select",
@@ -548,6 +640,8 @@
{
"fieldname": "price_list_currency",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
@@ -558,6 +652,8 @@
"description": "Rate at which Price list currency is converted to customer's base currency",
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Price List Exchange Rate",
"precision": "9",
"print_hide": 1,
@@ -567,6 +663,8 @@
"default": "0",
"fieldname": "ignore_pricing_rule",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
@@ -574,12 +672,16 @@
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"depends_on": "update_stock",
"fieldname": "set_warehouse",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Set Source Warehouse",
"options": "Warehouse",
"print_hide": 1
@@ -587,6 +689,8 @@
{
"fieldname": "items_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Section Break",
"options": "fa fa-shopping-cart"
},
@@ -594,6 +698,8 @@
"default": "0",
"fieldname": "update_stock",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Update Stock",
"oldfieldname": "update_stock",
"oldfieldtype": "Check",
@@ -602,12 +708,16 @@
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Scan Barcode"
},
{
"allow_bulk_edit": 1,
"fieldname": "items",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Items",
"oldfieldname": "entries",
"oldfieldtype": "Table",
@@ -617,11 +727,15 @@
{
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Pricing Rules"
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
"read_only": 1
@@ -629,6 +743,8 @@
{
"fieldname": "packing_list",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Packing List",
"options": "fa fa-suitcase",
"print_hide": 1
@@ -636,6 +752,8 @@
{
"fieldname": "packed_items",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Packed Items",
"options": "Packed Item",
"print_hide": 1
@@ -643,6 +761,8 @@
{
"fieldname": "product_bundle_help",
"fieldtype": "HTML",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Product Bundle Help",
"print_hide": 1
},
@@ -651,11 +771,15 @@
"collapsible_depends_on": "eval:doc.total_billing_amount > 0",
"fieldname": "time_sheet_list",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Time Sheet List"
},
{
"fieldname": "timesheets",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Time Sheets",
"options": "Sales Invoice Timesheet",
"print_hide": 1
@@ -664,23 +788,31 @@
"default": "0",
"fieldname": "total_billing_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Billing Amount",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "section_break_30",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Quantity",
"read_only": 1
},
{
"fieldname": "base_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
@@ -689,6 +821,8 @@
{
"fieldname": "base_net_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Net Total (Company Currency)",
"oldfieldname": "net_total",
"oldfieldtype": "Currency",
@@ -699,11 +833,15 @@
},
{
"fieldname": "column_break_32",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total",
"options": "currency",
"read_only": 1
@@ -711,6 +849,8 @@
{
"fieldname": "net_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Net Total",
"options": "currency",
"print_hide": 1,
@@ -719,6 +859,8 @@
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Net Weight",
"print_hide": 1,
"read_only": 1
@@ -726,12 +868,16 @@
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Section Break",
"options": "fa fa-money"
},
{
"fieldname": "taxes_and_charges",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Taxes and Charges Template",
"oldfieldname": "charge",
"oldfieldtype": "Link",
@@ -740,11 +886,15 @@
},
{
"fieldname": "column_break_38",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Shipping Rule",
"oldfieldtype": "Button",
"options": "Shipping Rule",
@@ -753,17 +903,23 @@
{
"fieldname": "tax_category",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Tax Category",
"options": "Tax Category",
"print_hide": 1
},
{
"fieldname": "section_break_40",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "taxes",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Taxes and Charges",
"oldfieldname": "other_charges",
"oldfieldtype": "Table",
@@ -773,11 +929,15 @@
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Tax Breakup"
},
{
"fieldname": "other_charges_calculation",
"fieldtype": "Long Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Taxes and Charges Calculation",
"no_copy": 1,
"oldfieldtype": "HTML",
@@ -786,11 +946,15 @@
},
{
"fieldname": "section_break_43",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Taxes and Charges (Company Currency)",
"oldfieldname": "other_charges_total",
"oldfieldtype": "Currency",
@@ -800,11 +964,15 @@
},
{
"fieldname": "column_break_47",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
@@ -814,12 +982,16 @@
"collapsible": 1,
"fieldname": "loyalty_points_redemption",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Loyalty Points Redemption"
},
{
"depends_on": "redeem_loyalty_points",
"fieldname": "loyalty_points",
"fieldtype": "Int",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Loyalty Points",
"no_copy": 1,
"print_hide": 1
@@ -828,6 +1000,8 @@
"depends_on": "redeem_loyalty_points",
"fieldname": "loyalty_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Loyalty Amount",
"no_copy": 1,
"options": "Company:company:default_currency",
@@ -838,18 +1012,24 @@
"default": "0",
"fieldname": "redeem_loyalty_points",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Redeem Loyalty Points",
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "column_break_77",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fetch_from": "customer.loyalty_program",
"fieldname": "loyalty_program",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Loyalty Program",
"no_copy": 1,
"options": "Loyalty Program",
@@ -860,6 +1040,8 @@
"depends_on": "redeem_loyalty_points",
"fieldname": "loyalty_redemption_account",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Redemption Account",
"no_copy": 1,
"options": "Account"
@@ -868,6 +1050,8 @@
"depends_on": "redeem_loyalty_points",
"fieldname": "loyalty_redemption_cost_center",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Redemption Cost Center",
"no_copy": 1,
"options": "Cost Center"
@@ -877,12 +1061,16 @@
"collapsible_depends_on": "discount_amount",
"fieldname": "section_break_49",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Additional Discount"
},
{
"default": "Grand Total",
"fieldname": "apply_discount_on",
"fieldtype": "Select",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
"print_hide": 1
@@ -890,6 +1078,8 @@
{
"fieldname": "base_discount_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
@@ -897,17 +1087,23 @@
},
{
"fieldname": "column_break_51",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Additional Discount Percentage",
"print_hide": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Additional Discount Amount",
"options": "currency",
"print_hide": 1
@@ -915,6 +1111,8 @@
{
"fieldname": "totals",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Section Break",
"options": "fa fa-money",
"print_hide": 1
@@ -922,6 +1120,8 @@
{
"fieldname": "base_grand_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Grand Total (Company Currency)",
"oldfieldname": "grand_total",
"oldfieldtype": "Currency",
@@ -933,6 +1133,8 @@
{
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Rounding Adjustment (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
@@ -942,6 +1144,8 @@
{
"fieldname": "base_rounded_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Rounded Total (Company Currency)",
"oldfieldname": "rounded_total",
"oldfieldtype": "Currency",
@@ -953,7 +1157,10 @@
"description": "In Words will be visible once you save the Sales Invoice.",
"fieldname": "base_in_words",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "In Words (Company Currency)",
+ "length": 240,
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -962,6 +1169,8 @@
{
"fieldname": "column_break5",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Column Break",
"print_hide": 1,
"width": "50%"
@@ -970,6 +1179,8 @@
"bold": 1,
"fieldname": "grand_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_list_view": 1,
"label": "Grand Total",
"oldfieldname": "grand_total_export",
@@ -981,6 +1192,8 @@
{
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Rounding Adjustment",
"no_copy": 1,
"options": "currency",
@@ -991,6 +1204,8 @@
"bold": 1,
"fieldname": "rounded_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Rounded Total",
"oldfieldname": "rounded_total_export",
"oldfieldtype": "Currency",
@@ -1000,7 +1215,10 @@
{
"fieldname": "in_words",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "In Words",
+ "length": 240,
"oldfieldname": "in_words_export",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -1009,6 +1227,8 @@
{
"fieldname": "total_advance",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Advance",
"oldfieldname": "total_advance",
"oldfieldtype": "Currency",
@@ -1019,6 +1239,8 @@
{
"fieldname": "outstanding_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Outstanding Amount",
"no_copy": 1,
"oldfieldname": "outstanding_amount",
@@ -1032,6 +1254,8 @@
"collapsible_depends_on": "advances",
"fieldname": "advances_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Advance Payments",
"oldfieldtype": "Section Break",
"options": "fa fa-money",
@@ -1041,18 +1265,24 @@
"default": "0",
"fieldname": "allocate_advances_automatically",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Allocate Advances Automatically (FIFO)"
},
{
"depends_on": "eval:!doc.allocate_advances_automatically",
"fieldname": "get_advances",
"fieldtype": "Button",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Get Advances Received",
"options": "set_advances"
},
{
"fieldname": "advances",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Advances",
"oldfieldname": "advance_adjustment_details",
"oldfieldtype": "Table",
@@ -1064,12 +1294,16 @@
"collapsible_depends_on": "eval:(!doc.is_pos && !doc.is_return)",
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Payment Terms"
},
{
"depends_on": "eval:(!doc.is_pos && !doc.is_return)",
"fieldname": "payment_terms_template",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Payment Terms Template",
"no_copy": 1,
"options": "Payment Terms Template",
@@ -1079,6 +1313,8 @@
"depends_on": "eval:(!doc.is_pos && !doc.is_return)",
"fieldname": "payment_schedule",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
@@ -1088,6 +1324,8 @@
"depends_on": "eval:doc.is_pos===1||(doc.advances && doc.advances.length>0)",
"fieldname": "payments_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Payments",
"options": "fa fa-money"
},
@@ -1096,6 +1334,8 @@
"fieldname": "cash_bank_account",
"fieldtype": "Link",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Cash/Bank Account",
"oldfieldname": "cash_bank_account",
"oldfieldtype": "Link",
@@ -1106,17 +1346,23 @@
"depends_on": "eval:doc.is_pos===1",
"fieldname": "payments",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Invoice Payment",
"options": "Sales Invoice Payment",
"print_hide": 1
},
{
"fieldname": "section_break_84",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "base_paid_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Paid Amount (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
@@ -1125,12 +1371,16 @@
},
{
"fieldname": "column_break_86",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"depends_on": "eval: doc.is_pos || doc.redeem_loyalty_points",
"fieldname": "paid_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Paid Amount",
"no_copy": 1,
"oldfieldname": "paid_amount",
@@ -1141,12 +1391,16 @@
},
{
"fieldname": "section_break_88",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"depends_on": "is_pos",
"fieldname": "base_change_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Base Change Amount (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
@@ -1155,12 +1409,16 @@
},
{
"fieldname": "column_break_90",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"depends_on": "is_pos",
"fieldname": "change_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Change Amount",
"no_copy": 1,
"options": "currency",
@@ -1170,6 +1428,8 @@
"depends_on": "is_pos",
"fieldname": "account_for_change_amount",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Account for Change Amount",
"options": "Account",
"print_hide": 1
@@ -1180,12 +1440,16 @@
"depends_on": "grand_total",
"fieldname": "column_break4",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Write Off",
"width": "50%"
},
{
"fieldname": "write_off_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
@@ -1194,6 +1458,8 @@
{
"fieldname": "base_write_off_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Write Off Amount (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
@@ -1205,16 +1471,22 @@
"depends_on": "is_pos",
"fieldname": "write_off_outstanding_amount_automatically",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Write Off Outstanding Amount",
"print_hide": 1
},
{
"fieldname": "column_break_74",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "write_off_account",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Write Off Account",
"options": "Account",
"print_hide": 1
@@ -1222,6 +1494,8 @@
{
"fieldname": "write_off_cost_center",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Write Off Cost Center",
"options": "Cost Center",
"print_hide": 1
@@ -1231,12 +1505,16 @@
"collapsible_depends_on": "terms",
"fieldname": "terms_section_break",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Terms and Conditions",
"oldfieldtype": "Section Break"
},
{
"fieldname": "tc_name",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Terms",
"oldfieldname": "tc_name",
"oldfieldtype": "Link",
@@ -1246,6 +1524,8 @@
{
"fieldname": "terms",
"fieldtype": "Text Editor",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Terms and Conditions Details",
"oldfieldname": "terms",
"oldfieldtype": "Text Editor"
@@ -1254,12 +1534,16 @@
"collapsible": 1,
"fieldname": "edit_printing_settings",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Printing Settings"
},
{
"allow_on_submit": 1,
"fieldname": "letter_head",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Letter Head",
"oldfieldname": "letter_head",
"oldfieldtype": "Select",
@@ -1271,24 +1555,32 @@
"default": "0",
"fieldname": "group_same_items",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Group same items",
"print_hide": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Print Language",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "column_break_84",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "select_print_heading",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Print Heading",
"no_copy": 1,
"oldfieldname": "select_print_heading",
@@ -1302,11 +1594,15 @@
"depends_on": "customer",
"fieldname": "more_information",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "More Information"
},
{
"fieldname": "inter_company_invoice_reference",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Inter Company Invoice Reference",
"options": "Purchase Invoice",
"read_only": 1
@@ -1315,6 +1611,8 @@
"fieldname": "customer_group",
"fieldtype": "Link",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Customer Group",
"options": "Customer Group",
"print_hide": 1
@@ -1322,6 +1620,8 @@
{
"fieldname": "campaign",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Campaign",
"oldfieldname": "campaign",
"oldfieldtype": "Link",
@@ -1332,6 +1632,8 @@
"default": "0",
"fieldname": "is_discounted",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Is Discounted",
"no_copy": 1,
"read_only": 1
@@ -1339,12 +1641,16 @@
{
"fieldname": "col_break23",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"width": "50%"
},
{
"default": "Draft",
"fieldname": "status",
"fieldtype": "Select",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
@@ -1355,6 +1661,8 @@
{
"fieldname": "source",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Source",
"oldfieldname": "source",
"oldfieldtype": "Select",
@@ -1365,6 +1673,8 @@
"collapsible": 1,
"fieldname": "more_info",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Accounting Details",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text",
@@ -1373,6 +1683,8 @@
{
"fieldname": "debit_to",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Debit To",
"oldfieldname": "debit_to",
"oldfieldtype": "Link",
@@ -1385,6 +1697,8 @@
"fieldname": "party_account_currency",
"fieldtype": "Link",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Party Account Currency",
"no_copy": 1,
"options": "Currency",
@@ -1395,6 +1709,8 @@
"default": "No",
"fieldname": "is_opening",
"fieldtype": "Select",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Is Opening Entry",
"oldfieldname": "is_opening",
"oldfieldtype": "Select",
@@ -1404,6 +1720,8 @@
{
"fieldname": "c_form_applicable",
"fieldtype": "Select",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "C-Form Applicable",
"no_copy": 1,
"options": "No\nYes",
@@ -1412,6 +1730,8 @@
{
"fieldname": "c_form_no",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "C-Form No",
"no_copy": 1,
"options": "C-Form",
@@ -1421,12 +1741,16 @@
{
"fieldname": "column_break8",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Column Break",
"print_hide": 1
},
{
"fieldname": "remarks",
"fieldtype": "Small Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Remarks",
"no_copy": 1,
"oldfieldname": "remarks",
@@ -1438,6 +1762,8 @@
"collapsible_depends_on": "sales_partner",
"fieldname": "sales_team_section_break",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Commission",
"oldfieldtype": "Section Break",
"options": "fa fa-group",
@@ -1446,6 +1772,8 @@
{
"fieldname": "sales_partner",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Partner",
"oldfieldname": "sales_partner",
"oldfieldtype": "Link",
@@ -1455,6 +1783,8 @@
{
"fieldname": "column_break10",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Column Break",
"print_hide": 1,
"width": "50%"
@@ -1462,6 +1792,8 @@
{
"fieldname": "commission_rate",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Commission Rate (%)",
"oldfieldname": "commission_rate",
"oldfieldtype": "Currency",
@@ -1470,6 +1802,8 @@
{
"fieldname": "total_commission",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Commission",
"oldfieldname": "total_commission",
"oldfieldtype": "Currency",
@@ -1481,6 +1815,8 @@
"collapsible_depends_on": "sales_team",
"fieldname": "section_break2",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Team",
"print_hide": 1
},
@@ -1488,6 +1824,8 @@
"allow_on_submit": 1,
"fieldname": "sales_team",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Team1",
"oldfieldname": "sales_team",
"oldfieldtype": "Table",
@@ -1495,14 +1833,19 @@
"print_hide": 1
},
{
+ "collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Subscription Section"
},
{
"allow_on_submit": 1,
"fieldname": "from_date",
"fieldtype": "Date",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "From Date",
"no_copy": 1,
"print_hide": 1
@@ -1511,18 +1854,24 @@
"allow_on_submit": 1,
"fieldname": "to_date",
"fieldtype": "Date",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "To Date",
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "column_break_140",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "auto_repeat",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Auto Repeat",
"no_copy": 1,
"options": "Auto Repeat",
@@ -1534,12 +1883,16 @@
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Update Auto Repeat Reference"
},
{
"fieldname": "against_income_account",
"fieldtype": "Small Text",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Against Income Account",
"no_copy": 1,
"oldfieldname": "against_income_account",
@@ -1551,6 +1904,8 @@
"fieldname": "pos_total_qty",
"fieldtype": "Float",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Qty",
"print_hide": 1,
"print_hide_if_no_value": 1,
@@ -1560,17 +1915,30 @@
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "is_consolidated",
+ "fieldtype": "Check",
+ "label": "Is Consolidated",
+ "read_only": 1
},
{
"default": "0",
"fetch_from": "customer.is_internal_customer",
"fieldname": "is_internal_customer",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Is Internal Customer",
"read_only": 1
}
@@ -1579,7 +1947,7 @@
"idx": 181,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-19 17:00:57.208696",
+ "modified": "2020-07-18 05:07:16.725974",
"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 5e8279b..3dab054 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -8,8 +8,6 @@
from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date
from frappe.model.mapper import get_mapped_doc
-from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option
-
from erpnext.controllers.selling_controller import SellingController
from erpnext.accounts.utils import get_account_currency
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
@@ -133,7 +131,7 @@
if self.is_pos and self.is_return:
self.verify_payment_amount_is_negative()
- if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
+ if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points and not self.is_consolidated:
validate_loyalty_points(self, self.loyalty_points)
def validate_fixed_asset(self):
@@ -200,13 +198,13 @@
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
- if not self.is_return and self.loyalty_program:
+ if not self.is_return and not self.is_consolidated and self.loyalty_program:
self.make_loyalty_point_entry()
- elif self.is_return and self.return_against and self.loyalty_program:
+ elif self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program:
against_si_doc = frappe.get_doc("Sales Invoice", self.return_against)
against_si_doc.delete_loyalty_point_entry()
against_si_doc.make_loyalty_point_entry()
- if self.redeem_loyalty_points and self.loyalty_points:
+ if self.redeem_loyalty_points and not self.is_consolidated and self.loyalty_points:
self.apply_loyalty_points()
# Healthcare Service Invoice.
@@ -265,9 +263,9 @@
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
update_company_current_month_sales(self.company)
self.update_project()
- if not self.is_return and self.loyalty_program:
+ if not self.is_return and not self.is_consolidated and self.loyalty_program:
self.delete_loyalty_point_entry()
- elif self.is_return and self.return_against and self.loyalty_program:
+ elif self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program:
against_si_doc = frappe.get_doc("Sales Invoice", self.return_against)
against_si_doc.delete_loyalty_point_entry()
against_si_doc.make_loyalty_point_entry()
@@ -347,7 +345,7 @@
super(SalesInvoice, self).set_missing_values(for_validate)
- print_format = pos.get("print_format_for_online") if pos else None
+ print_format = pos.get("print_format") if pos else None
if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')):
print_format = 'POS Invoice'
@@ -420,8 +418,6 @@
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
if pos:
- self.allow_print_before_pay = pos.allow_print_before_pay
-
if not for_validate:
self.tax_category = pos.get("tax_category")
@@ -432,8 +428,8 @@
if pos.get('account_for_change_amount'):
self.account_for_change_amount = pos.get('account_for_change_amount')
- for fieldname in ('territory', 'naming_series', 'currency', 'letter_head', 'tc_name',
- 'company', 'select_print_heading', 'cash_bank_account', 'write_off_account', 'taxes_and_charges',
+ for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name',
+ 'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
if (not for_validate) or (for_validate and not self.get(fieldname)):
self.set(fieldname, pos.get(fieldname))
@@ -790,7 +786,8 @@
if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
- "cost_center": self.cost_center
+ "cost_center": self.cost_center,
+ "project": self.project
}, self.party_account_currency, item=self)
)
@@ -845,7 +842,8 @@
"credit_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"))),
- "cost_center": item.cost_center
+ "cost_center": item.cost_center,
+ "project": item.project or self.project
}, account_currency, item=item)
)
@@ -926,7 +924,8 @@
if self.party_account_currency==self.company_currency else flt(self.change_amount),
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
- "cost_center": self.cost_center
+ "cost_center": self.cost_center,
+ "project": self.project
}, self.party_account_currency, item=self)
)
@@ -959,7 +958,8 @@
else flt(self.write_off_amount, self.precision("write_off_amount"))),
"against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype,
- "cost_center": self.cost_center
+ "cost_center": self.cost_center,
+ "project": self.project
}, self.party_account_currency, item=self)
)
gl_entries.append(
@@ -1109,14 +1109,18 @@
expiry_date=self.posting_date, include_expired_entry=True)
if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \
(not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)):
- points_earned = cint(eligible_amount/lp_details.collection_factor)
+
+ collection_factor = lp_details.collection_factor if lp_details.collection_factor else 1.0
+ points_earned = cint(eligible_amount/collection_factor)
+
doc = frappe.get_doc({
"doctype": "Loyalty Point Entry",
"company": self.company,
"loyalty_program": lp_details.loyalty_program,
"loyalty_program_tier": lp_details.tier_name,
"customer": self.customer,
- "sales_invoice": self.name,
+ "invoice_type": self.doctype,
+ "invoice": self.name,
"loyalty_points": points_earned,
"purchase_amount": eligible_amount,
"expiry_date": add_days(self.posting_date, lp_details.expiry_duration),
@@ -1128,18 +1132,18 @@
# valdite the redemption and then delete the loyalty points earned on cancel of the invoice
def delete_loyalty_point_entry(self):
- lp_entry = frappe.db.sql("select name from `tabLoyalty Point Entry` where sales_invoice=%s",
+ lp_entry = frappe.db.sql("select name from `tabLoyalty Point Entry` where invoice=%s",
(self.name), as_dict=1)
if not lp_entry: return
- against_lp_entry = frappe.db.sql('''select name, sales_invoice from `tabLoyalty Point Entry`
+ against_lp_entry = frappe.db.sql('''select name, invoice from `tabLoyalty Point Entry`
where redeem_against=%s''', (lp_entry[0].name), as_dict=1)
if against_lp_entry:
- invoice_list = ", ".join([d.sales_invoice for d in against_lp_entry])
- frappe.throw(_('''Sales Invoice can't be cancelled since the Loyalty Points earned has been redeemed.
- First cancel the Sales Invoice No {0}''').format(invoice_list))
+ invoice_list = ", ".join([d.invoice for d in against_lp_entry])
+ frappe.throw(_('''{} can't be cancelled since the Loyalty Points earned has been redeemed.
+ First cancel the {} No {}''').format(self.doctype, self.doctype, invoice_list))
else:
- frappe.db.sql('''delete from `tabLoyalty Point Entry` where sales_invoice=%s''', (self.name))
+ frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name))
# Set loyalty program
self.set_loyalty_program_tier()
@@ -1165,7 +1169,9 @@
points_to_redeem = self.loyalty_points
for lp_entry in loyalty_point_entries:
- if lp_entry.sales_invoice == self.name:
+ if lp_entry.invoice_type != self.doctype or lp_entry.invoice == self.name:
+ # redeemption should be done against same doctype
+ # also it shouldn't be against itself
continue
available_points = lp_entry.loyalty_points - flt(redemption_details.get(lp_entry.name))
if available_points > points_to_redeem:
@@ -1178,7 +1184,8 @@
"loyalty_program": self.loyalty_program,
"loyalty_program_tier": lp_entry.loyalty_program_tier,
"customer": self.customer,
- "sales_invoice": self.name,
+ "invoice_type": self.doctype,
+ "invoice": self.name,
"redeem_against": lp_entry.name,
"loyalty_points": -1*redeemed_points,
"purchase_amount": self.grand_total,
@@ -1569,13 +1576,13 @@
from erpnext.selling.doctype.customer.customer import get_loyalty_programs
customer = frappe.get_doc('Customer', customer)
- if customer.loyalty_program: return
+ if customer.loyalty_program: return [customer.loyalty_program]
lp_details = get_loyalty_programs(customer)
if len(lp_details) == 1:
frappe.db.set(customer, 'loyalty_program', lp_details[0])
- return []
+ return lp_details
else:
return lp_details
@@ -1595,3 +1602,71 @@
})
return invoice_discounting
+
+def update_multi_mode_option(doc, pos_profile):
+ def append_payment(payment_mode):
+ payment = doc.append('payments', {})
+ payment.default = payment_mode.default
+ payment.mode_of_payment = payment_mode.parent
+ payment.account = payment_mode.default_account
+ payment.type = payment_mode.type
+
+ doc.set('payments', [])
+ if not pos_profile or not pos_profile.get('payments'):
+ for payment_mode in get_all_mode_of_payments(doc):
+ append_payment(payment_mode)
+ return
+
+ for pos_payment_method in pos_profile.get('payments'):
+ pos_payment_method = pos_payment_method.as_dict()
+
+ payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
+ payment_mode[0].default = pos_payment_method.default
+ append_payment(payment_mode[0])
+
+def get_all_mode_of_payments(doc):
+ return frappe.db.sql("""
+ select mpa.default_account, mpa.parent, mp.type as type
+ from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
+ where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
+ {'company': doc.company}, as_dict=1)
+
+def get_mode_of_payment_info(mode_of_payment, company):
+ return frappe.db.sql("""
+ select mpa.default_account, mpa.parent, mp.type as type
+ from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
+ where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
+ (company, mode_of_payment), as_dict=1)
+
+def create_dunning(source_name, target_doc=None):
+ from frappe.model.mapper import get_mapped_doc
+ from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount
+ def set_missing_values(source, target):
+ target.sales_invoice = source_name
+ target.outstanding_amount = source.outstanding_amount
+ overdue_days = (getdate(target.posting_date) - getdate(source.due_date)).days
+ target.overdue_days = overdue_days
+ if frappe.db.exists('Dunning Type', {'start_day': [
+ '<', overdue_days], 'end_day': ['>=', overdue_days]}):
+ dunning_type = frappe.get_doc('Dunning Type', {'start_day': [
+ '<', overdue_days], 'end_day': ['>=', overdue_days]})
+ target.dunning_type = dunning_type.name
+ target.rate_of_interest = dunning_type.rate_of_interest
+ target.dunning_fee = dunning_type.dunning_fee
+ letter_text = get_dunning_letter_text(dunning_type = dunning_type.name, doc = target.as_dict())
+ if letter_text:
+ target.body_text = letter_text.get('body_text')
+ target.closing_text = letter_text.get('closing_text')
+ target.language = letter_text.get('language')
+ amounts = calculate_interest_and_amount(target.posting_date, target.outstanding_amount,
+ target.rate_of_interest, target.dunning_fee, target.overdue_days)
+ target.interest_amount = amounts.get('interest_amount')
+ target.dunning_amount = amounts.get('dunning_amount')
+ target.grand_total = amounts.get('grand_total')
+
+ doclist = get_mapped_doc("Sales Invoice", source_name, {
+ "Sales Invoice": {
+ "doctype": "Dunning",
+ }
+ }, target_doc, set_missing_values)
+ return doclist
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
index 4a8fcc0..f106928 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
@@ -18,7 +18,7 @@
'transactions': [
{
'label': _('Payment'),
- 'items': ['Payment Entry', 'Payment Request', 'Journal Entry', 'Invoice Discounting']
+ 'items': ['Payment Entry', 'Payment Request', 'Journal Entry', 'Invoice Discounting', 'Dunning']
},
{
'label': _('Reference'),
diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json
index ebe6e3d..11ebe6a 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_records.json
+++ b/erpnext/accounts/doctype/sales_invoice/test_records.json
@@ -3,6 +3,7 @@
"company": "_Test Company",
"conversion_rate": 1.0,
"currency": "INR",
+ "cost_center": "_Test Cost Center - _TC",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
"debit_to": "_Test Receivable - _TC",
@@ -37,7 +38,8 @@
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
- "parentfield": "taxes",
+ "parentfield": "taxes",
+ "cost_center": "_Test Cost Center - _TC",
"rate": 6
},
{
@@ -45,7 +47,8 @@
"charge_type": "On Net Total",
"description": "Service Tax",
"doctype": "Sales Taxes and Charges",
- "parentfield": "taxes",
+ "parentfield": "taxes",
+ "cost_center": "_Test Cost Center - _TC",
"rate": 6.36
}
],
@@ -76,6 +79,7 @@
"customer_name": "_Test Customer",
"debit_to": "_Test Receivable - _TC",
"doctype": "Sales Invoice",
+ "cost_center": "_Test Cost Center - _TC",
"items": [
{
"amount": 500.0,
@@ -107,7 +111,8 @@
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
- "parentfield": "taxes",
+ "parentfield": "taxes",
+ "cost_center": "_Test Cost Center - _TC",
"rate": 16
},
{
@@ -115,7 +120,8 @@
"charge_type": "On Net Total",
"description": "Service Tax",
"doctype": "Sales Taxes and Charges",
- "parentfield": "taxes",
+ "parentfield": "taxes",
+ "cost_center": "_Test Cost Center - _TC",
"rate": 10
}
],
@@ -132,6 +138,7 @@
"customer_name": "_Test Customer",
"debit_to": "_Test Receivable - _TC",
"doctype": "Sales Invoice",
+ "cost_center": "_Test Cost Center - _TC",
"items": [
{
"cost_center": "_Test Cost Center - _TC",
@@ -259,6 +266,7 @@
"customer_name": "_Test Customer",
"debit_to": "_Test Receivable - _TC",
"doctype": "Sales Invoice",
+ "cost_center": "_Test Cost Center - _TC",
"items": [
{
"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 311cc12..964566a 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -706,37 +706,15 @@
self.pos_gl_entry(si, pos, 50)
- def test_pos_returns_without_repayment(self):
- pos_profile = make_pos_profile()
-
- pos = create_sales_invoice(qty = 10, do_not_save=True)
- pos.is_pos = 1
- pos.pos_profile = pos_profile.name
-
- pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
- pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
- pos.insert()
- pos.submit()
-
- pos_return = create_sales_invoice(is_return=1,
- return_against=pos.name, qty=-5, do_not_save=True)
-
- pos_return.is_pos = 1
- pos_return.pos_profile = pos_profile.name
-
- pos_return.insert()
- pos_return.submit()
-
- self.assertFalse(pos_return.is_pos)
- self.assertFalse(pos_return.get('payments'))
-
def test_pos_returns_with_repayment(self):
+ from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
+
pos_profile = make_pos_profile()
+ pos_profile.payments = []
pos_profile.append('payments', {
'default': 1,
- 'mode_of_payment': 'Cash',
- 'amount': 0.0
+ 'mode_of_payment': 'Cash'
})
pos_profile.save()
@@ -751,18 +729,12 @@
pos.insert()
pos.submit()
- pos_return = create_sales_invoice(is_return=1,
- return_against=pos.name, qty=-5, do_not_save=True)
+ pos_return = make_sales_return(pos.name)
- pos_return.is_pos = 1
- pos_return.pos_profile = pos_profile.name
pos_return.insert()
pos_return.submit()
- self.assertEqual(pos_return.get('payments')[0].amount, -500)
- pos_profile.payments = []
- pos_profile.save()
-
+ self.assertEqual(pos_return.get('payments')[0].amount, -1000)
def test_pos_change_amount(self):
make_pos_profile()
@@ -788,82 +760,6 @@
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
-
- pos_profile = make_pos_profile()
-
- pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
- 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)
-
- invoice_data = [{'09052016142': pos}]
- si = make_invoice(pos_profile, invoice_data).get('invoice')
- self.assertEqual(si[0], '09052016142')
-
- 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, 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
-
- 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)
-
- pos_profile = make_pos_profile()
- timestamp = cint(time.time())
-
- item = make_item("_Test POS Item")
- pos = copy.deepcopy(test_records[1])
- pos['items'][0]['item_code'] = item.name
- pos['items'][0]['warehouse'] = "_Test Warehouse - _TC"
- pos["is_pos"] = 1
- pos["offline_pos_name"] = timestamp
- 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}]
-
- invoice_data = [{timestamp: pos}]
- si = make_invoice(pos_profile, invoice_data).get('invoice')
- self.assertEqual(si[0], timestamp)
-
- sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp})
- self.assertEqual(sales_invoice[0].docstatus, 0)
-
- timestamp = cint(time.time())
- pos["offline_pos_name"] = timestamp
- invoice_data = [{timestamp: pos}]
- si1 = make_invoice(pos_profile, invoice_data).get('invoice')
- self.assertEqual(si1[0], timestamp)
-
- sales_invoice1 = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp})
- self.assertEqual(sales_invoice1[0].docstatus, 0)
-
- if allow_negative_stock:
- frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
-
def pos_gl_entry(self, si, pos, cash_amount):
# check stock ledger entries
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
@@ -1640,11 +1536,8 @@
si_doc = frappe.get_doc('Sales Invoice', si.name)
self.assertEqual(si_doc.outstanding_amount, 0)
- def test_sales_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ def test_sales_invoice_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
@@ -1669,14 +1562,47 @@
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+ def test_sales_invoice_with_project_link(self):
+ from erpnext.projects.doctype.project.test_project import make_project
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
+ project = make_project({
+ 'project_name': 'Sales Invoice Project',
+ 'project_template_name': 'Test Project Template',
+ 'start_date': '2020-01-01'
+ })
+ item_project = make_project({
+ 'project_name': 'Sales Invoice Item Project',
+ 'project_template_name': 'Test Project Template',
+ 'start_date': '2019-06-01'
+ })
- def test_sales_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
+ sales_invoice = create_sales_invoice(do_not_save=1)
+ sales_invoice.items[0].project = item_project.project_name
+ sales_invoice.project = project.project_name
+
+ sales_invoice.submit()
+
+ expected_values = {
+ "Debtors - _TC": {
+ "project": project.project_name
+ },
+ "Sales - _TC": {
+ "project": item_project.project_name
+ }
+ }
+
+ gl_entries = frappe.db.sql("""select account, cost_center, project, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
+ order by account asc""", sales_invoice.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(expected_values[gle.account]["project"], gle.project)
+
+ def test_sales_invoice_without_cost_center(self):
cost_center = "_Test Cost Center - _TC"
si = create_sales_invoice(debit_to="Debtors - _TC")
@@ -1699,9 +1625,6 @@
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
-
def test_deferred_revenue(self):
deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company")
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 b2294e4..004d358 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -94,6 +94,7 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
+ "project",
"section_break_54",
"page_break"
],
@@ -783,12 +784,18 @@
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2019-12-04 12:22:38.517710",
+ "modified": "2020-07-18 12:24:41.749986",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json
index 52cf810..5ab46b7 100644
--- a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json
+++ b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json
@@ -1,314 +1,91 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-05-08 23:49:38.842621",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-05-08 23:49:38.842621",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "default",
+ "mode_of_payment",
+ "amount",
+ "column_break_3",
+ "account",
+ "type",
+ "base_amount",
+ "clearance_date"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:parent.doctype == 'POS Profile'",
- "fetch_if_empty": 0,
- "fieldname": "default",
- "fieldtype": "Check",
- "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": "Default",
- "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": "mode_of_payment",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Mode of Payment",
+ "options": "Mode of Payment",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "mode_of_payment",
- "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": "Mode of Payment",
- "length": 0,
- "no_copy": 0,
- "options": "Mode of Payment",
- "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
- },
+ "default": "0",
+ "depends_on": "eval:parent.doctype == 'Sales Invoice'",
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Amount",
+ "options": "currency",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "depends_on": "eval:parent.doctype == 'Sales Invoice'",
- "fetch_if_empty": 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": 1,
- "in_standard_filter": 0,
- "label": "Amount",
- "length": 0,
- "no_copy": 0,
- "options": "currency",
- "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": "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,
- "fetch_if_empty": 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": "account",
+ "fieldtype": "Link",
+ "label": "Account",
+ "options": "Account",
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "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": "Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "fetch_from": "mode_of_payment.type",
+ "fieldname": "type",
+ "fieldtype": "Read Only",
+ "label": "Type"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "mode_of_payment.type",
- "fetch_if_empty": 0,
- "fieldname": "type",
- "fieldtype": "Read Only",
- "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": "Type",
- "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
- },
+ "fieldname": "base_amount",
+ "fieldtype": "Currency",
+ "label": "Base Amount (Company Currency)",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "base_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": "Base Amount (Company Currency)",
- "length": 0,
- "no_copy": 1,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "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": "clearance_date",
+ "fieldtype": "Date",
+ "label": "Clearance Date",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "clearance_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": "Clearance Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "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
+ "default": "0",
+ "fieldname": "default",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Default",
+ "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": 1,
- "max_attachments": 0,
- "modified": "2019-03-19 14:54:56.524556",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Sales Invoice Payment",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-08-03 12:45:39.986598",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Sales Invoice Payment",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/subscription/subscription.js b/erpnext/accounts/doctype/subscription/subscription.js
index dcbec12..ba98eb9 100644
--- a/erpnext/accounts/doctype/subscription/subscription.js
+++ b/erpnext/accounts/doctype/subscription/subscription.js
@@ -2,6 +2,16 @@
// For license information, please see license.txt
frappe.ui.form.on('Subscription', {
+ setup: function(frm) {
+ frm.set_query('party_type', function() {
+ return {
+ filters : {
+ name: ['in', ['Customer', 'Supplier']]
+ }
+ }
+ });
+ },
+
refresh: function(frm) {
if(!frm.is_new()){
if(frm.doc.status !== 'Cancelled'){
diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json
index 32b97ba..afb94fe 100644
--- a/erpnext/accounts/doctype/subscription/subscription.json
+++ b/erpnext/accounts/doctype/subscription/subscription.json
@@ -6,14 +6,18 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "customer",
- "cb_1",
+ "party_type",
"status",
+ "cb_1",
+ "party",
"subscription_period",
- "start",
+ "start_date",
+ "end_date",
"cancelation_date",
"trial_period_start",
"trial_period_end",
+ "follow_calendar_months",
+ "generate_new_invoices_past_due_date",
"column_break_11",
"current_invoice_start",
"current_invoice_end",
@@ -23,7 +27,8 @@
"sb_4",
"plans",
"sb_1",
- "tax_template",
+ "sales_tax_template",
+ "purchase_tax_template",
"sb_2",
"apply_additional_discount",
"cb_2",
@@ -32,19 +37,11 @@
"sb_3",
"invoices",
"accounting_dimensions_section",
+ "cost_center",
"dimension_col_break"
],
"fields": [
{
- "fieldname": "customer",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Customer",
- "options": "Customer",
- "reqd": 1,
- "set_only_once": 1
- },
- {
"allow_on_submit": 1,
"fieldname": "cb_1",
"fieldtype": "Column Break"
@@ -53,7 +50,7 @@
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
- "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid",
+ "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
"read_only": 1
},
{
@@ -62,12 +59,6 @@
"label": "Subscription Period"
},
{
- "fieldname": "start",
- "fieldtype": "Date",
- "label": "Subscription Start Date",
- "set_only_once": 1
- },
- {
"fieldname": "cancelation_date",
"fieldtype": "Date",
"label": "Cancelation Date",
@@ -137,17 +128,12 @@
"reqd": 1
},
{
+ "depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)",
"fieldname": "sb_1",
"fieldtype": "Section Break",
"label": "Taxes"
},
{
- "fieldname": "tax_template",
- "fieldtype": "Link",
- "label": "Sales Taxes and Charges Template",
- "options": "Sales Taxes and Charges Template"
- },
- {
"fieldname": "sb_2",
"fieldtype": "Section Break",
"label": "Discounts"
@@ -195,10 +181,74 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "party_type",
+ "fieldtype": "Link",
+ "label": "Party Type",
+ "options": "DocType",
+ "reqd": 1,
+ "set_only_once": 1
+ },
+ {
+ "fieldname": "party",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Party",
+ "options": "party_type",
+ "reqd": 1,
+ "set_only_once": 1
+ },
+ {
+ "depends_on": "eval:doc.party_type === 'Customer'",
+ "fieldname": "sales_tax_template",
+ "fieldtype": "Link",
+ "label": "Sales Taxes and Charges Template",
+ "options": "Sales Taxes and Charges Template"
+ },
+ {
+ "depends_on": "eval:doc.party_type === 'Supplier'",
+ "fieldname": "purchase_tax_template",
+ "fieldtype": "Link",
+ "label": "Purchase Taxes and Charges Template",
+ "options": "Purchase Taxes and Charges Template"
+ },
+ {
+ "default": "0",
+ "description": "If this is checked subsequent new invoices will be created on calendar month and quarter start dates irrespective of current invoice start date",
+ "fieldname": "follow_calendar_months",
+ "fieldtype": "Check",
+ "label": "Follow Calendar Months",
+ "set_only_once": 1
+ },
+ {
+ "default": "0",
+ "description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
+ "fieldname": "generate_new_invoices_past_due_date",
+ "fieldtype": "Check",
+ "label": "Generate New Invoices Past Due Date"
+ },
+ {
+ "fieldname": "end_date",
+ "fieldtype": "Date",
+ "label": "Subscription End Date",
+ "set_only_once": 1
+ },
+ {
+ "fieldname": "start_date",
+ "fieldtype": "Date",
+ "label": "Subscription Start Date",
+ "set_only_once": 1
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
}
],
"links": [],
- "modified": "2020-01-27 14:37:32.845173",
+ "modified": "2020-06-25 10:52:52.265105",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 0933c7e..0752531 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -7,7 +7,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils.data import nowdate, getdate, cint, add_days, date_diff, get_last_day, add_to_date, flt
+from frappe.utils.data import nowdate, getdate, cstr, cint, add_days, date_diff, get_last_day, add_to_date, flt
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
@@ -15,7 +15,7 @@
class Subscription(Document):
def before_insert(self):
# update start just before the subscription doc is created
- self.update_subscription_period(self.start)
+ self.update_subscription_period(self.start_date)
def update_subscription_period(self, date=None):
"""
@@ -35,7 +35,9 @@
If the `date` parameter is not given , it will be automatically set as today's
date.
"""
- if self.trial_period_start and self.is_trialling():
+ if self.is_new_subscription() and self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date):
+ self.current_invoice_start = add_days(self.trial_period_end, 1)
+ elif self.trial_period_start and self.is_trialling():
self.current_invoice_start = self.trial_period_start
elif date:
self.current_invoice_start = date
@@ -53,15 +55,45 @@
current billing period where `x` is the billing interval from the
`Subscription Plan` in the `Subscription`.
"""
- if self.is_trialling():
+ if self.is_trialling() and getdate(self.current_invoice_start) < getdate(self.trial_period_end):
self.current_invoice_end = self.trial_period_end
else:
billing_cycle_info = self.get_billing_cycle_data()
if billing_cycle_info:
- self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info)
+ if self.is_new_subscription() and getdate(self.start_date) < getdate(self.current_invoice_start):
+ self.current_invoice_end = add_to_date(self.start_date, **billing_cycle_info)
+
+ # For cases where trial period is for an entire billing interval
+ if getdate(self.current_invoice_end) < getdate(self.current_invoice_start):
+ self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info)
+ else:
+ self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info)
else:
self.current_invoice_end = get_last_day(self.current_invoice_start)
+ if self.follow_calendar_months:
+ billing_info = self.get_billing_cycle_and_interval()
+ billing_interval_count = billing_info[0]['billing_interval_count']
+ calendar_months = get_calendar_months(billing_interval_count)
+ calendar_month = 0
+ current_invoice_end_month = getdate(self.current_invoice_end).month
+ current_invoice_end_year = getdate(self.current_invoice_end).year
+
+ for month in calendar_months:
+ if month <= current_invoice_end_month:
+ calendar_month = month
+
+ if cint(calendar_month - billing_interval_count) <= 0 and \
+ getdate(self.current_invoice_start).month != 1:
+ calendar_month = 12
+ current_invoice_end_year -= 1
+
+ self.current_invoice_end = get_last_day(cstr(current_invoice_end_year) + '-' \
+ + cstr(calendar_month) + '-01')
+
+ if self.end_date and getdate(self.current_invoice_end) > getdate(self.end_date):
+ self.current_invoice_end = self.end_date
+
@staticmethod
def validate_plans_billing_cycle(billing_cycle_data):
"""
@@ -132,21 +164,22 @@
"""
if self.is_trialling():
self.status = 'Trialling'
- elif self.status == 'Past Due Date' and self.is_past_grace_period():
+ elif self.status == 'Active' and self.end_date and getdate() > getdate(self.end_date):
+ self.status = 'Completed'
+ elif self.is_past_grace_period():
subscription_settings = frappe.get_single('Subscription Settings')
self.status = 'Cancelled' if cint(subscription_settings.cancel_after_grace) else 'Unpaid'
- elif self.status == 'Past Due Date' and not self.has_outstanding_invoice():
- self.status = 'Active'
- elif self.current_invoice_is_past_due():
+ elif self.current_invoice_is_past_due() and not self.is_past_grace_period():
self.status = 'Past Due Date'
+ elif not self.has_outstanding_invoice():
+ self.status = 'Active'
elif self.is_new_subscription():
self.status = 'Active'
- # todo: then generate new invoice
self.save()
def is_trialling(self):
"""
- Returns `True` if the `Subscription` is trial period.
+ Returns `True` if the `Subscription` is in trial period.
"""
return not self.period_has_passed(self.trial_period_end) and self.is_new_subscription()
@@ -160,7 +193,7 @@
return True
end_date = getdate(end_date)
- return getdate(nowdate()) > getdate(end_date)
+ return getdate() > getdate(end_date)
def is_past_grace_period(self):
"""
@@ -171,7 +204,7 @@
subscription_settings = frappe.get_single('Subscription Settings')
grace_period = cint(subscription_settings.grace_period)
- return getdate(nowdate()) > add_days(current_invoice.due_date, grace_period)
+ return getdate() > add_days(current_invoice.due_date, grace_period)
def current_invoice_is_past_due(self, current_invoice=None):
"""
@@ -180,22 +213,24 @@
if not current_invoice:
current_invoice = self.get_current_invoice()
- if not current_invoice:
+ if not current_invoice or self.is_paid(current_invoice):
return False
else:
- return getdate(nowdate()) > getdate(current_invoice.due_date)
+ return getdate() > getdate(current_invoice.due_date)
def get_current_invoice(self):
"""
Returns the most recent generated invoice.
"""
+ doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice'
+
if len(self.invoices):
current = self.invoices[-1]
- if frappe.db.exists('Sales Invoice', current.invoice):
- doc = frappe.get_doc('Sales Invoice', current.invoice)
+ if frappe.db.exists(doctype, current.get('invoice')):
+ doc = frappe.get_doc(doctype, current.get('invoice'))
return doc
else:
- frappe.throw(_('Invoice {0} no longer exists').format(current.invoice))
+ frappe.throw(_('Invoice {0} no longer exists').format(current.get('invoice')))
def is_new_subscription(self):
"""
@@ -206,6 +241,8 @@
def validate(self):
self.validate_trial_period()
self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval())
+ self.validate_end_date()
+ self.validate_to_follow_calendar_months()
def validate_trial_period(self):
"""
@@ -215,34 +252,72 @@
if getdate(self.trial_period_end) < getdate(self.trial_period_start):
frappe.throw(_('Trial Period End Date Cannot be before Trial Period Start Date'))
- elif self.trial_period_start or self.trial_period_end:
+ if self.trial_period_start and not self.trial_period_end:
frappe.throw(_('Both Trial Period Start Date and Trial Period End Date must be set'))
+ if self.trial_period_start and getdate(self.trial_period_start) > getdate(self.start_date):
+ frappe.throw(_('Trial Period Start date cannot be after Subscription Start Date'))
+
+ def validate_end_date(self):
+ billing_cycle_info = self.get_billing_cycle_data()
+ end_date = add_to_date(self.start_date, **billing_cycle_info)
+
+ if self.end_date and getdate(self.end_date) <= getdate(end_date):
+ frappe.throw(_('Subscription End Date must be after {0} as per the subscription plan').format(end_date))
+
+ def validate_to_follow_calendar_months(self):
+ if self.follow_calendar_months:
+ billing_info = self.get_billing_cycle_and_interval()
+
+ if not self.end_date:
+ frappe.throw(_('Subscription End Date is mandatory to follow calendar months'))
+
+ if billing_info[0]['billing_interval'] != 'Month':
+ frappe.throw('Billing Interval in Subscription Plan must be Month to follow calendar months')
+
def after_insert(self):
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
self.set_subscription_status()
def generate_invoice(self, prorate=0):
"""
- Creates a `Sales Invoice` for the `Subscription`, updates `self.invoices` and
+ Creates a `Invoice` for the `Subscription`, updates `self.invoices` and
saves the `Subscription`.
"""
+
+ doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice'
+
invoice = self.create_invoice(prorate)
- self.append('invoices', {'invoice': invoice.name})
+ self.append('invoices', {
+ 'document_type': doctype,
+ 'invoice': invoice.name
+ })
+
self.save()
return invoice
def create_invoice(self, prorate):
"""
- Creates a `Sales Invoice`, submits it and returns it
+ Creates a `Invoice`, submits it and returns it
"""
- invoice = frappe.new_doc('Sales Invoice')
- invoice.set_posting_time = 1
- invoice.posting_date = self.current_invoice_start
- invoice.customer = self.customer
+ doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice'
- ## Add dimesnions in invoice for subscription:
+ invoice = frappe.new_doc(doctype)
+ invoice.set_posting_time = 1
+ invoice.posting_date = self.current_invoice_start if self.generate_invoice_at_period_start \
+ else self.current_invoice_end
+
+ invoice.cost_center = self.cost_center
+
+ if doctype == 'Sales Invoice':
+ invoice.customer = self.party
+ else:
+ invoice.supplier = self.party
+ if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'):
+ invoice.apply_tds = 1
+
+ ## Add dimensions in invoice for subscription:
accounting_dimensions = get_accounting_dimensions()
for dimension in accounting_dimensions:
@@ -255,18 +330,25 @@
# for that reason
items_list = self.get_items_from_plans(self.plans, prorate)
for item in items_list:
- invoice.append('items', item)
+ invoice.append('items', item)
# Taxes
- if self.tax_template:
- invoice.taxes_and_charges = self.tax_template
+ tax_template = ''
+
+ if doctype == 'Sales Invoice' and self.sales_tax_template:
+ tax_template = self.sales_tax_template
+ if doctype == 'Purchase Invoice' and self.purchase_tax_template:
+ tax_template = self.purchase_tax_template
+
+ if tax_template:
+ invoice.taxes_and_charges = tax_template
invoice.set_taxes()
# Due date
invoice.append(
'payment_schedule',
{
- 'due_date': add_days(self.current_invoice_end, cint(self.days_until_due)),
+ 'due_date': add_days(invoice.posting_date, cint(self.days_until_due)),
'invoice_portion': 100
}
)
@@ -300,13 +382,42 @@
prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start)
items = []
- customer = self.customer
+ party = self.party
for plan in plans:
- item_code = frappe.db.get_value("Subscription Plan", plan.plan, "item")
- if not prorate:
- items.append({'item_code': item_code, 'qty': plan.qty, 'rate': get_plan_rate(plan.plan, plan.qty, customer)})
+ plan_doc = frappe.get_doc('Subscription Plan', plan.plan)
+
+ item_code = plan_doc.item
+
+ if self.party == 'Customer':
+ deferred_field = 'enable_deferred_revenue'
else:
- items.append({'item_code': item_code, 'qty': plan.qty, 'rate': (get_plan_rate(plan.plan, plan.qty, customer) * prorate_factor)})
+ deferred_field = 'enable_deferred_expense'
+
+ deferred = frappe.db.get_value('Item', item_code, deferred_field)
+
+ if not prorate:
+ item = {'item_code': item_code, 'qty': plan.qty, 'rate': get_plan_rate(plan.plan, plan.qty, party,
+ self.current_invoice_start, self.current_invoice_end), 'cost_center': plan_doc.cost_center}
+ else:
+ item = {'item_code': item_code, 'qty': plan.qty, 'rate': get_plan_rate(plan.plan, plan.qty, party,
+ self.current_invoice_start, self.current_invoice_end, prorate_factor), 'cost_center': plan_doc.cost_center}
+
+ if deferred:
+ item.update({
+ deferred_field: deferred,
+ 'service_start_date': self.current_invoice_start,
+ 'service_end_date': self.current_invoice_end
+ })
+
+ accounting_dimensions = get_accounting_dimensions()
+
+ for dimension in accounting_dimensions:
+ if plan_doc.get(dimension):
+ item.update({
+ dimension: plan_doc.get(dimension)
+ })
+
+ items.append(item)
return items
@@ -322,12 +433,13 @@
elif self.status in ['Past Due Date', 'Unpaid']:
self.process_for_past_due_date()
+ self.set_subscription_status()
+
self.save()
def is_postpaid_to_invoice(self):
- return getdate(nowdate()) > getdate(self.current_invoice_end) or \
- (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and \
- not self.has_outstanding_invoice()
+ return getdate() > getdate(self.current_invoice_end) or \
+ (getdate() >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start))
def is_prepaid_to_invoice(self):
if not self.generate_invoice_at_period_start:
@@ -337,14 +449,12 @@
return True
# 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()
+ return getdate() >= getdate(self.current_invoice_start)
- def is_current_invoice_paid(self):
- if self.is_new_subscription():
- return False
+ def is_current_invoice_generated(self):
+ invoice = self.get_current_invoice()
- 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':
+ if invoice and getdate(self.current_invoice_start) <= getdate(invoice.posting_date) <= getdate(self.current_invoice_end):
return True
return False
@@ -358,21 +468,23 @@
2. Change the `Subscription` status to 'Past Due Date'
3. Change the `Subscription` status to 'Cancelled'
"""
- 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'
+ if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice():
+ self.update_subscription_period(add_days(self.current_invoice_end, 1))
- if self.current_invoice_is_past_due() and getdate(nowdate()) > getdate(self.current_invoice_end):
- self.status = 'Past Due Date'
+ if not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()):
+ prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
+ self.generate_invoice(prorate)
- if self.cancel_at_period_end and getdate(nowdate()) > getdate(self.current_invoice_end):
+ if self.cancel_at_period_end and getdate() > getdate(self.current_invoice_end):
self.cancel_subscription_at_period_end()
def cancel_subscription_at_period_end(self):
"""
Called when `Subscription.cancel_at_period_end` is truthy
"""
+ if self.end_date and getdate() < getdate(self.end_date):
+ return
+
self.status = 'Cancelled'
if not self.cancelation_date:
self.cancelation_date = nowdate()
@@ -390,14 +502,22 @@
if not current_invoice:
frappe.throw(_('Current invoice {0} is missing').format(current_invoice.invoice))
else:
- if self.is_not_outstanding(current_invoice):
+ if not self.has_outstanding_invoice():
self.status = 'Active'
- self.update_subscription_period(add_days(self.current_invoice_end, 1))
else:
self.set_status_grace_period()
+ if getdate() > getdate(self.current_invoice_end):
+ self.update_subscription_period(add_days(self.current_invoice_end, 1))
+
+ # Generate invoices periodically even if current invoice are unpaid
+ if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice()
+ or self.is_prepaid_to_invoice()):
+ prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
+ self.generate_invoice(prorate)
+
@staticmethod
- def is_not_outstanding(invoice):
+ def is_paid(invoice):
"""
Return `True` if the given invoice is paid
"""
@@ -407,11 +527,17 @@
"""
Returns `True` if the most recent invoice for the `Subscription` is not paid
"""
+ doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice'
current_invoice = self.get_current_invoice()
- if not current_invoice:
- return False
+ invoice_list = [d.invoice for d in self.invoices]
+
+ outstanding_invoices = frappe.get_all(doctype, fields=['name'],
+ filters={'status': ('!=', 'Paid'), 'name': ('in', invoice_list)})
+
+ if outstanding_invoices:
+ return True
else:
- return not self.is_not_outstanding(current_invoice)
+ False
def cancel_subscription(self):
"""
@@ -419,7 +545,7 @@
but it will not affect already created invoices.
"""
if self.status != 'Cancelled':
- to_generate_invoice = True if self.status == 'Active' else False
+ to_generate_invoice = True if self.status == 'Active' and not self.generate_invoice_at_period_start else False
to_prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
self.status = 'Cancelled'
self.cancelation_date = nowdate()
@@ -435,7 +561,7 @@
"""
if self.status == 'Cancelled':
self.status = 'Active'
- self.db_set('start', nowdate())
+ self.db_set('start_date', nowdate())
self.update_subscription_period(nowdate())
self.invoices = []
self.save()
@@ -447,6 +573,14 @@
if invoice:
return invoice.precision('grand_total')
+def get_calendar_months(billing_interval):
+ calendar_months = []
+ start = 0
+ while start < 12:
+ start += billing_interval
+ calendar_months.append(start)
+
+ return calendar_months
def get_prorata_factor(period_end, period_start):
diff = flt(date_diff(nowdate(), period_start) + 1)
@@ -469,10 +603,7 @@
"""
Returns all `Subscription` documents
"""
- return frappe.db.sql(
- 'select name from `tabSubscription` where status != "Cancelled"',
- as_dict=1
- )
+ return frappe.db.get_all('Subscription', {'status': ('!=','Cancelled')})
def process(data):
diff --git a/erpnext/accounts/doctype/subscription/subscription_list.js b/erpnext/accounts/doctype/subscription/subscription_list.js
index abcfc5e..a4edb77 100644
--- a/erpnext/accounts/doctype/subscription/subscription_list.js
+++ b/erpnext/accounts/doctype/subscription/subscription_list.js
@@ -4,6 +4,8 @@
return [__("Trialling"), "green"];
} else if(doc.status === 'Active') {
return [__("Active"), "green"];
+ } else if(doc.status === 'Completed') {
+ return [__("Completed"), "green"];
} else if(doc.status === 'Past Due Date') {
return [__("Past Due Date"), "orange"];
} else if(doc.status === 'Unpaid') {
diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py
index 3d96f23..f41f08a 100644
--- a/erpnext/accounts/doctype/subscription/test_subscription.py
+++ b/erpnext/accounts/doctype/subscription/test_subscription.py
@@ -7,7 +7,7 @@
import frappe
from erpnext.accounts.doctype.subscription.subscription import get_prorata_factor
-from frappe.utils.data import nowdate, add_days, add_to_date, add_months, date_diff, flt
+from frappe.utils.data import nowdate, add_days, add_to_date, add_months, date_diff, flt, get_date_str
def create_plan():
@@ -15,7 +15,7 @@
plan = frappe.new_doc('Subscription Plan')
plan.plan_name = '_Test Plan Name'
plan.item = '_Test Non Stock Item'
- plan.price_determination = "Fixed rate"
+ plan.price_determination = "Fixed Rate"
plan.cost = 900
plan.billing_interval = 'Month'
plan.billing_interval_count = 1
@@ -25,7 +25,7 @@
plan = frappe.new_doc('Subscription Plan')
plan.plan_name = '_Test Plan Name 2'
plan.item = '_Test Non Stock Item'
- plan.price_determination = "Fixed rate"
+ plan.price_determination = "Fixed Rate"
plan.cost = 1999
plan.billing_interval = 'Month'
plan.billing_interval_count = 1
@@ -35,12 +35,29 @@
plan = frappe.new_doc('Subscription Plan')
plan.plan_name = '_Test Plan Name 3'
plan.item = '_Test Non Stock Item'
- plan.price_determination = "Fixed rate"
+ plan.price_determination = "Fixed Rate"
plan.cost = 1999
plan.billing_interval = 'Day'
plan.billing_interval_count = 14
plan.insert()
+ # Defined a quarterly Subscription Plan
+ if not frappe.db.exists('Subscription Plan', '_Test Plan Name 4'):
+ plan = frappe.new_doc('Subscription Plan')
+ plan.plan_name = '_Test Plan Name 4'
+ plan.item = '_Test Non Stock Item'
+ plan.price_determination = "Monthly Rate"
+ plan.cost = 20000
+ plan.billing_interval = 'Month'
+ plan.billing_interval_count = 3
+ plan.insert()
+
+ if not frappe.db.exists('Supplier', '_Test Supplier'):
+ supplier = frappe.new_doc('Supplier')
+ supplier.supplier_name = '_Test Supplier'
+ supplier.supplier_group = 'All Supplier Groups'
+ supplier.insert()
+
class TestSubscription(unittest.TestCase):
def setUp(self):
@@ -48,7 +65,8 @@
def test_create_subscription_with_trial_with_correct_period(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.trial_period_start = nowdate()
subscription.trial_period_end = add_days(nowdate(), 30)
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
@@ -56,8 +74,8 @@
self.assertEqual(subscription.trial_period_start, nowdate())
self.assertEqual(subscription.trial_period_end, add_days(nowdate(), 30))
- self.assertEqual(subscription.trial_period_start, subscription.current_invoice_start)
- self.assertEqual(subscription.trial_period_end, subscription.current_invoice_end)
+ self.assertEqual(add_days(subscription.trial_period_end, 1), get_date_str(subscription.current_invoice_start))
+ self.assertEqual(add_days(subscription.current_invoice_start, 30), get_date_str(subscription.current_invoice_end))
self.assertEqual(subscription.invoices, [])
self.assertEqual(subscription.status, 'Trialling')
@@ -65,7 +83,8 @@
def test_create_subscription_without_trial_with_correct_period(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
@@ -81,7 +100,8 @@
def test_create_subscription_trial_with_wrong_dates(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.trial_period_end = nowdate()
subscription.trial_period_start = add_days(nowdate(), 30)
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
@@ -91,7 +111,8 @@
def test_create_subscription_multi_with_different_billing_fails(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.trial_period_end = nowdate()
subscription.trial_period_start = add_days(nowdate(), 30)
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
@@ -102,8 +123,9 @@
def test_invoice_is_generated_at_end_of_billing_period(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
- subscription.start = '2018-01-01'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
+ subscription.start_date = '2018-01-01'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.insert()
@@ -114,18 +136,22 @@
self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.current_invoice_start, '2018-01-01')
- self.assertEqual(subscription.status, 'Past Due Date')
+ subscription.process()
+ self.assertEqual(subscription.status, 'Unpaid')
subscription.delete()
def test_status_goes_back_to_active_after_invoice_is_paid(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start = '2018-01-01'
+ subscription.start_date = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
self.assertEqual(len(subscription.invoices), 1)
- self.assertEqual(subscription.status, 'Past Due Date')
+
+ # Status is unpaid as Days until Due is zero and grace period is Zero
+ self.assertEqual(subscription.status, 'Unpaid')
subscription.get_current_invoice()
current_invoice = subscription.get_current_invoice()
@@ -137,7 +163,7 @@
subscription.process()
self.assertEqual(subscription.status, 'Active')
- self.assertEqual(subscription.current_invoice_start, add_months(subscription.start, 1))
+ self.assertEqual(subscription.current_invoice_start, add_months(subscription.start_date, 1))
self.assertEqual(len(subscription.invoices), 1)
subscription.delete()
@@ -149,16 +175,17 @@
settings.save()
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start = '2018-01-01'
+ subscription.start_date = '2018-01-01'
subscription.insert()
+
+ self.assertEqual(subscription.status, 'Active')
+
subscription.process() # generate first invoice
-
- self.assertEqual(subscription.status, 'Past Due Date')
-
- subscription.process()
# This should change status to Cancelled since grace period is 0
+ # And is backdated subscription so subscription will be cancelled after processing
self.assertEqual(subscription.status, 'Cancelled')
settings.cancel_after_grace = default_grace_period_action
@@ -172,16 +199,14 @@
settings.save()
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start = '2018-01-01'
+ subscription.start_date = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
- self.assertEqual(subscription.status, 'Past Due Date')
-
- subscription.process()
- # This should change status to Cancelled since grace period is 0
+ # Status is unpaid as Days until Due is zero and grace period is Zero
self.assertEqual(subscription.status, 'Unpaid')
settings.cancel_after_grace = default_grace_period_action
@@ -190,10 +215,11 @@
def test_subscription_invoice_days_until_due(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.days_until_due = 10
- subscription.start = add_months(nowdate(), -1)
+ subscription.start_date = add_months(nowdate(), -1)
subscription.insert()
subscription.process() # generate first invoice
self.assertEqual(len(subscription.invoices), 1)
@@ -208,9 +234,10 @@
settings.save()
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start = '2018-01-01'
+ subscription.start_date = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
@@ -232,7 +259,8 @@
def test_subscription_remains_active_during_invoice_period(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
subscription.process() # no changes expected
@@ -258,7 +286,8 @@
def test_subscription_cancelation(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
subscription.cancel_subscription()
@@ -274,7 +303,8 @@
settings.save()
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
@@ -309,7 +339,8 @@
settings.save()
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
subscription.cancel_subscription()
@@ -329,7 +360,8 @@
settings.save()
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
subscription.cancel_subscription()
@@ -353,16 +385,14 @@
settings.save()
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start = '2018-01-01'
+ subscription.start_date = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
invoices = len(subscription.invoices)
- self.assertEqual(subscription.status, 'Past Due Date')
- self.assertEqual(len(subscription.invoices), invoices)
-
subscription.cancel_subscription()
self.assertEqual(subscription.status, 'Cancelled')
self.assertEqual(len(subscription.invoices), invoices)
@@ -387,15 +417,14 @@
settings.save()
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start = '2018-01-01'
+ subscription.start_date = '2018-01-01'
subscription.insert()
subscription.process() # generate first invoice
- self.assertEqual(subscription.status, 'Past Due Date')
-
- subscription.process()
+ # Status is unpaid as Days until Due is zero and grace period is Zero
self.assertEqual(subscription.status, 'Unpaid')
subscription.cancel_subscription()
@@ -424,16 +453,14 @@
settings.save()
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
- subscription.start = '2018-01-01'
+ subscription.start_date = '2018-01-01'
subscription.insert()
+
subscription.process() # generate first invoice
-
- self.assertEqual(subscription.status, 'Past Due Date')
-
- subscription.process()
- # This should change status to Cancelled since grace period is 0
+ # This should change status to Unpaid since grace period is 0
self.assertEqual(subscription.status, 'Unpaid')
invoice = subscription.get_current_invoice()
@@ -445,7 +472,7 @@
# A new invoice is generated
subscription.process()
- self.assertEqual(subscription.status, 'Past Due Date')
+ self.assertEqual(subscription.status, 'Unpaid')
settings.cancel_after_grace = default_grace_period_action
settings.save()
@@ -453,7 +480,8 @@
def test_restart_active_subscription(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
@@ -463,7 +491,8 @@
def test_subscription_invoice_discount_percentage(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.additional_discount_percentage = 10
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
@@ -478,7 +507,8 @@
def test_subscription_invoice_discount_amount(self):
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.additional_discount_amount = 11
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
@@ -495,7 +525,8 @@
# Create a non pre-billed subscription, processing should not create
# invoices.
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
subscription.process()
@@ -517,10 +548,12 @@
settings.save()
subscription = frappe.new_doc('Subscription')
- subscription.customer = '_Test Customer'
+ subscription.party_type = 'Customer'
+ subscription.party = '_Test Customer'
subscription.generate_invoice_at_period_start = True
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.save()
+ subscription.process()
subscription.cancel_subscription()
self.assertEqual(len(subscription.invoices), 1)
@@ -538,3 +571,65 @@
settings.save()
subscription.delete()
+
+ def test_subscription_with_follow_calendar_months(self):
+ subscription = frappe.new_doc('Subscription')
+ subscription.party_type = 'Supplier'
+ subscription.party = '_Test Supplier'
+ subscription.generate_invoice_at_period_start = 1
+ subscription.follow_calendar_months = 1
+
+ # select subscription start date as '2018-01-15'
+ subscription.start_date = '2018-01-15'
+ subscription.end_date = '2018-07-15'
+ subscription.append('plans', {'plan': '_Test Plan Name 4', 'qty': 1})
+ subscription.save()
+
+ # even though subscription starts at '2018-01-15' and Billing interval is Month and count 3
+ # First invoice will end at '2018-03-31' instead of '2018-04-14'
+ self.assertEqual(get_date_str(subscription.current_invoice_end), '2018-03-31')
+
+ def test_subscription_generate_invoice_past_due(self):
+ subscription = frappe.new_doc('Subscription')
+ subscription.party_type = 'Supplier'
+ subscription.party = '_Test Supplier'
+ subscription.generate_invoice_at_period_start = 1
+ subscription.generate_new_invoices_past_due_date = 1
+ # select subscription start date as '2018-01-15'
+ subscription.start_date = '2018-01-01'
+ subscription.append('plans', {'plan': '_Test Plan Name 4', 'qty': 1})
+ subscription.save()
+
+ # Process subscription and create first invoice
+ # Subscription status will be unpaid since due date has already passed
+ subscription.process()
+ self.assertEqual(len(subscription.invoices), 1)
+ self.assertEqual(subscription.status, 'Unpaid')
+
+ # Now the Subscription is unpaid
+ # Even then new invoice should be created as we have enabled `generate_new_invoices_past_due_date` in
+ # subscription
+
+ subscription.process()
+ self.assertEqual(len(subscription.invoices), 2)
+
+ def test_subscription_without_generate_invoice_past_due(self):
+ subscription = frappe.new_doc('Subscription')
+ subscription.party_type = 'Supplier'
+ subscription.party = '_Test Supplier'
+ subscription.generate_invoice_at_period_start = 1
+ # select subscription start date as '2018-01-15'
+ subscription.start_date = '2018-01-01'
+ subscription.append('plans', {'plan': '_Test Plan Name 4', 'qty': 1})
+ subscription.save()
+
+ # Process subscription and create first invoice
+ # Subscription status will be unpaid since due date has already passed
+ subscription.process()
+ self.assertEqual(len(subscription.invoices), 1)
+ self.assertEqual(subscription.status, 'Unpaid')
+
+ subscription.process()
+ self.assertEqual(len(subscription.invoices), 1)
+
+
diff --git a/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json b/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json
index c4bae1d..f54e887 100644
--- a/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json
+++ b/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json
@@ -1,73 +1,40 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-02-26 04:21:41.265055",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2018-02-26 04:21:41.265055",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "document_type",
+ "invoice"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "invoice",
- "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": "Invoice",
- "length": 0,
- "no_copy": 0,
- "options": "Sales Invoice",
- "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": "document_type",
+ "fieldtype": "Link",
+ "label": "Document Type ",
+ "options": "DocType",
+ "read_only": 1
+ },
+ {
+ "fieldname": "invoice",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Invoice",
+ "options": "document_type",
+ "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": 1,
- "max_attachments": 0,
- "modified": "2018-02-26 10:48:07.033422",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Subscription Invoice",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-06-01 22:23:54.462718",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Subscription Invoice",
+ "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/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
index 9f79066..46ce093 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_rename": 1,
"autoname": "field:plan_name",
"creation": "2018-02-24 11:31:23.066506",
@@ -24,6 +25,7 @@
"column_break_16",
"payment_gateway",
"accounting_dimensions_section",
+ "cost_center",
"dimension_col_break"
],
"fields": [
@@ -60,8 +62,8 @@
{
"fieldname": "price_determination",
"fieldtype": "Select",
- "label": "Price Determination",
- "options": "\nFixed rate\nBased on price list",
+ "label": "Subscription Price Based On",
+ "options": "\nFixed Rate\nBased On Price List\nMonthly Rate",
"reqd": 1
},
{
@@ -69,7 +71,7 @@
"fieldtype": "Column Break"
},
{
- "depends_on": "eval:doc.price_determination==\"Fixed rate\"",
+ "depends_on": "eval:['Fixed Rate', 'Monthly Rate'].includes(doc.price_determination)",
"fieldname": "cost",
"fieldtype": "Currency",
"in_list_view": 1,
@@ -136,9 +138,16 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
}
],
- "modified": "2019-07-25 18:35:04.362556",
+ "links": [],
+ "modified": "2020-06-25 10:53:44.205774",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan",
@@ -155,6 +164,30 @@
"role": "System Manager",
"share": 1,
"write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
+ "write": 1
}
],
"sort_field": "modified",
diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
index 625979b..1ca442a 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
@@ -5,6 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
+from frappe.utils import get_first_day, get_last_day, date_diff, flt, getdate
from frappe.model.document import Document
from erpnext.utilities.product import get_price
@@ -17,12 +18,12 @@
frappe.throw(_('Billing Interval Count cannot be less than 1'))
@frappe.whitelist()
-def get_plan_rate(plan, quantity=1, customer=None):
+def get_plan_rate(plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1):
plan = frappe.get_doc("Subscription Plan", plan)
- if plan.price_determination == "Fixed rate":
- return plan.cost
+ if plan.price_determination == "Fixed Rate":
+ return plan.cost * prorate_factor
- elif plan.price_determination == "Based on price list":
+ elif plan.price_determination == "Based On Price List":
if customer:
customer_group = frappe.db.get_value("Customer", customer, "customer_group")
else:
@@ -32,4 +33,25 @@
if not price:
return 0
else:
- return price.price_list_rate
+ return price.price_list_rate * prorate_factor
+
+ elif plan.price_determination == 'Monthly Rate':
+ start_date = getdate(start_date)
+ end_date = getdate(end_date)
+
+ no_of_months = (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) + 1
+ cost = plan.cost * no_of_months
+
+ # Adjust cost if start or end date is not month start or end
+ prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
+
+ if prorate:
+ prorate_factor = flt(date_diff(start_date, get_first_day(start_date)) / date_diff(
+ get_last_day(start_date), get_first_day(start_date)), 1)
+
+ prorate_factor += flt(date_diff(get_last_day(end_date), end_date) / date_diff(
+ get_last_day(end_date), get_first_day(end_date)), 1)
+
+ cost -= (plan.cost * prorate_factor)
+
+ return cost
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json b/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json
index ca54a16..3e16303 100644
--- a/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json
+++ b/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json
@@ -1,106 +1,40 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-02-25 07:35:07.736146",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2018-02-25 07:35:07.736146",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "plan",
+ "qty"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "qty",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Quantity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "qty",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Quantity",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "plan",
- "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": "Plan",
- "length": 0,
- "no_copy": 0,
- "options": "Subscription Plan",
- "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": "plan",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Plan",
+ "options": "Subscription Plan",
+ "reqd": 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": 1,
- "max_attachments": 0,
- "modified": "2018-06-20 15:35:13.514699",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Subscription Plan Detail",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-06-14 17:44:05.275100",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Subscription Plan Detail",
+ "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/accounts/doctype/subscription_settings/subscription_settings.json b/erpnext/accounts/doctype/subscription_settings/subscription_settings.json
index 8c7c6f3..821db7e 100644
--- a/erpnext/accounts/doctype/subscription_settings/subscription_settings.json
+++ b/erpnext/accounts/doctype/subscription_settings/subscription_settings.json
@@ -1,179 +1,76 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-02-26 06:13:37.910139",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2018-02-26 06:13:37.910139",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "grace_period",
+ "cancel_after_grace",
+ "prorate"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "1",
- "description": "Number of days after invoice date has elapsed before canceling subscription or marking subscription as unpaid",
- "fieldname": "grace_period",
- "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": "Grace Period",
- "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
- },
+ "default": "1",
+ "description": "Number of days after invoice date has elapsed before canceling subscription or marking subscription as unpaid",
+ "fieldname": "grace_period",
+ "fieldtype": "Int",
+ "label": "Grace Period"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fieldname": "cancel_after_grace",
- "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": "Cancel Invoice After Grace Period",
- "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
- },
+ "default": "0",
+ "fieldname": "cancel_after_grace",
+ "fieldtype": "Check",
+ "label": "Cancel Subscription After Grace Period"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "1",
- "fieldname": "prorate",
- "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": "Prorate",
- "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
+ "default": "1",
+ "fieldname": "prorate",
+ "fieldtype": "Check",
+ "label": "Prorate"
}
- ],
- "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": "2018-02-26 13:58:09.455832",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Subscription Settings",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "issingle": 1,
+ "links": [],
+ "modified": "2020-06-23 09:13:44.292792",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Subscription Settings",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "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,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System 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,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Administrator",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Accounts Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Accounts 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/general_ledger.py b/erpnext/accounts/general_ledger.py
index bfe35ab..cf3deb8 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -140,10 +140,8 @@
gle = frappe.new_doc("GL Entry")
gle.update(args)
gle.flags.ignore_permissions = 1
- gle.validate()
- gle.db_insert()
+ gle.insert()
gle.run_method("on_update_with_args", adv_adj, update_outstanding)
- gle.flags.ignore_validate = True
gle.submit()
# check against budget
@@ -160,8 +158,10 @@
if account not in aii_accounts:
continue
+ # Always use current date to get stock and account balance as there can future entries for
+ # other items
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
- gl_map[0].posting_date, gl_map[0].company)
+ getdate(), gl_map[0].company)
if gl_map[0].voucher_type=="Journal Entry":
# In case of Journal Entry, there are no corresponding SL entries,
@@ -171,7 +171,6 @@
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
.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 --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json
index 12da440..ba1a779 100644
--- a/erpnext/accounts/module_onboarding/accounts/accounts.json
+++ b/erpnext/accounts/module_onboarding/accounts/accounts.json
@@ -13,7 +13,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
"idx": 0,
"is_complete": 0,
- "modified": "2020-05-14 22:11:06.475938",
+ "modified": "2020-07-08 14:06:09.033880",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts",
@@ -44,8 +44,7 @@
"step": "Configure Account Settings"
}
],
- "subtitle": "Accounts, invoices and taxation.",
- "success_message": "The Accounts module is now set up!",
- "title": "Let's Setup Your Accounts and Taxes.",
- "user_can_dismiss": 1
+ "subtitle": "Accounts, Invoices, Taxation, and more.",
+ "success_message": "The Accounts Module is all set up!",
+ "title": "Let's Set Up Your Accounts and Taxes."
}
\ No newline at end of file
diff --git a/erpnext/accounts/number_card/total_incoming_bills/total_incoming_bills.json b/erpnext/accounts/number_card/total_incoming_bills/total_incoming_bills.json
new file mode 100644
index 0000000..283e187
--- /dev/null
+++ b/erpnext/accounts/number_card/total_incoming_bills/total_incoming_bills.json
@@ -0,0 +1,21 @@
+{
+ "aggregate_function_based_on": "base_net_total",
+ "creation": "2020-07-17 11:25:34.748329",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Purchase Invoice",
+ "filters_json": "[[\"Purchase Invoice\",\"docstatus\",\"=\",\"1\",false],[\"Purchase Invoice\",\"posting_date\",\"Timespan\",\"this year\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Incoming Bills",
+ "modified": "2020-07-22 13:06:46.045344",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Total Incoming Bills",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/number_card/total_incoming_payment/total_incoming_payment.json b/erpnext/accounts/number_card/total_incoming_payment/total_incoming_payment.json
new file mode 100644
index 0000000..bc23c15
--- /dev/null
+++ b/erpnext/accounts/number_card/total_incoming_payment/total_incoming_payment.json
@@ -0,0 +1,21 @@
+{
+ "aggregate_function_based_on": "base_received_amount",
+ "creation": "2020-07-17 11:25:34.673195",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Payment Entry",
+ "filters_json": "[[\"Payment Entry\",\"docstatus\",\"=\",\"1\",false],[\"Payment Entry\",\"posting_date\",\"Timespan\",\"this year\",false],[\"Payment Entry\",\"payment_type\",\"=\",\"Receive\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Incoming Payment",
+ "modified": "2020-07-22 13:06:20.237689",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Total Incoming Payment",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/number_card/total_outgoing_bills/total_outgoing_bills.json b/erpnext/accounts/number_card/total_outgoing_bills/total_outgoing_bills.json
new file mode 100644
index 0000000..fe91618
--- /dev/null
+++ b/erpnext/accounts/number_card/total_outgoing_bills/total_outgoing_bills.json
@@ -0,0 +1,21 @@
+{
+ "aggregate_function_based_on": "base_net_total",
+ "creation": "2020-07-17 11:25:34.725416",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Sales Invoice",
+ "filters_json": "[[\"Sales Invoice\",\"docstatus\",\"=\",\"1\",false],[\"Sales Invoice\",\"posting_date\",\"Timespan\",\"this year\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Outgoing Bills",
+ "modified": "2020-07-22 13:07:19.633101",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Total Outgoing Bills",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/number_card/total_outgoing_payment/total_outgoing_payment.json b/erpnext/accounts/number_card/total_outgoing_payment/total_outgoing_payment.json
new file mode 100644
index 0000000..d27be88
--- /dev/null
+++ b/erpnext/accounts/number_card/total_outgoing_payment/total_outgoing_payment.json
@@ -0,0 +1,21 @@
+{
+ "aggregate_function_based_on": "base_paid_amount",
+ "creation": "2020-07-17 11:25:34.700137",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Payment Entry",
+ "filters_json": "[[\"Payment Entry\",\"docstatus\",\"=\",\"1\",false],[\"Payment Entry\",\"posting_date\",\"Timespan\",\"this year\",false],[\"Payment Entry\",\"payment_type\",\"=\",\"Pay\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Outgoing Payment",
+ "modified": "2020-07-22 12:49:34.942896",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Total Outgoing Payment",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json
index bb396d2..5a403b0 100644
--- a/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json
+++ b/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json
@@ -8,7 +8,7 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-14 17:46:41.831517",
+ "modified": "2020-06-01 13:16:19.731719",
"modified_by": "Administrator",
"name": "Create a Customer",
"owner": "Administrator",
diff --git a/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json b/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json
index 450bee1..d2068e1 100644
--- a/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json
+++ b/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json
@@ -1,6 +1,6 @@
{
"action": "Create Entry",
- "creation": "2020-05-14 17:45:28.554605",
+ "creation": "2020-05-12 18:16:06.624554",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
@@ -8,7 +8,7 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-14 17:45:28.554605",
+ "modified": "2020-05-12 18:30:02.489949",
"modified_by": "Administrator",
"name": "Create a Product",
"owner": "Administrator",
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
index 69f9907..ce6baa6 100644
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
+++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py
@@ -21,7 +21,7 @@
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"))
@@ -289,6 +289,8 @@
else:
return []
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def payment_entry_query(doctype, txt, searchfield, start, page_len, filters):
account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account")
if not account:
@@ -317,6 +319,8 @@
}
)
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def journal_entry_query(doctype, txt, searchfield, start, page_len, filters):
account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account")
@@ -352,6 +356,8 @@
}
)
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""
SELECT
diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js
deleted file mode 100755
index 24fcb41..0000000
--- a/erpnext/accounts/page/pos/pos.js
+++ /dev/null
@@ -1,2105 +0,0 @@
-frappe.provide("erpnext.pos");
-{% include "erpnext/public/js/controllers/taxes_and_totals.js" %}
-
-frappe.pages['pos'].on_page_load = function (wrapper) {
- var page = frappe.ui.make_app_page({
- parent: wrapper,
- title: __('Point of Sale'),
- single_column: true
- });
-
- frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => {
- if (r && r.use_pos_in_offline_mode && cint(r.use_pos_in_offline_mode)) {
- // offline
- wrapper.pos = new erpnext.pos.PointOfSale(wrapper);
- cur_pos = wrapper.pos;
- } else {
- // online
- frappe.flags.is_online = true
- frappe.set_route('point-of-sale');
- }
- });
-}
-
-frappe.pages['pos'].refresh = function (wrapper) {
- window.onbeforeunload = function () {
- return wrapper.pos.beforeunload()
- }
-
- if (frappe.flags.is_online) {
- frappe.set_route('point-of-sale');
- }
-}
-
-erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
- init: function (wrapper) {
- this.page_len = 20;
- this.freeze = false;
- this.page = wrapper.page;
- this.wrapper = $(wrapper).find('.page-content');
- this.set_indicator();
- this.onload();
- this.make_menu_list();
- this.bind_events();
- this.bind_items_event();
- this.si_docs = this.get_doc_from_localstorage();
- },
-
- beforeunload: function (e) {
- if (this.connection_status == false && frappe.get_route()[0] == "pos") {
- e = e || window.event;
-
- // For IE and Firefox prior to version 4
- if (e) {
- e.returnValue = __("You are in offline mode. You will not be able to reload until you have network.");
- return
- }
-
- // For Safari
- return __("You are in offline mode. You will not be able to reload until you have network.");
- }
- },
-
- check_internet_connection: function () {
- var me = this;
- //Check Internet connection after every 30 seconds
- setInterval(function () {
- me.set_indicator();
- }, 5000)
- },
-
- set_indicator: function () {
- var me = this;
- // navigator.onLine
- this.connection_status = false;
- this.page.set_indicator(__("Offline"), "grey")
- frappe.call({
- method: "frappe.handler.ping",
- callback: function (r) {
- if (r.message) {
- me.connection_status = true;
- me.page.set_indicator(__("Online"), "green")
- }
- }
- })
- },
-
- onload: function () {
- var me = this;
- this.get_data_from_server(function () {
- me.make_control();
- me.create_new();
- me.make();
- });
- },
-
- make_menu_list: function () {
- var me = this;
- this.page.clear_menu();
-
- // for mobile
- this.page.add_menu_item(__("Pay"), function () {
- me.validate();
- me.update_paid_amount_status(true);
- me.create_invoice();
- me.make_payment();
- }).addClass('visible-xs');
-
- this.page.add_menu_item(__("New Sales Invoice"), function () {
- me.save_previous_entry();
- me.create_new();
- })
-
- this.page.add_menu_item(__("Sync Master Data"), function () {
- me.get_data_from_server(function () {
- me.load_data(false);
- me.make_item_list();
- me.set_missing_values();
- })
- });
-
- this.page.add_menu_item(__("Sync Offline Invoices"), function () {
- me.freeze_screen = true;
- me.sync_sales_invoice()
- });
-
- this.page.add_menu_item(__("Cashier Closing"), function () {
- frappe.set_route('List', 'Cashier Closing');
- });
-
- this.page.add_menu_item(__("POS Profile"), function () {
- frappe.set_route('List', 'POS Profile');
- });
- },
-
- email_prompt: function() {
- var me = this;
- var fields = [{label:__("To"), fieldtype:"Data", reqd: 0, fieldname:"recipients",length:524288},
- {fieldtype: "Section Break", collapsible: 1, label: "CC & Email Template"},
- {fieldtype: "Section Break"},
- {label:__("Subject"), fieldtype:"Data", reqd: 1,
- fieldname:"subject",length:524288},
- {fieldtype: "Section Break"},
- {label:__("Message"), fieldtype:"Text Editor", reqd: 1,
- fieldname:"content"},
- {fieldtype: "Section Break"},
- {fieldtype: "Column Break"}];
-
- this.email_dialog = new frappe.ui.Dialog({
- title: "Email",
- fields: fields,
- primary_action_label: __("Send"),
- primary_action: function() {
- me.send_action();
- }
- });
-
- this.email_dialog.show()
- },
-
- send_action: function() {
- this.email_queue = this.get_email_queue()
- this.email_queue[this.frm.doc.offline_pos_name] = JSON.stringify(this.email_dialog.get_values())
- this.update_email_queue()
- this.email_dialog.hide()
- },
-
- update_email_queue: function () {
- try {
- localStorage.setItem('email_queue', JSON.stringify(this.email_queue));
- } catch (e) {
- frappe.throw(__("LocalStorage is full, did not save"))
- }
- },
-
- get_email_queue: function () {
- try {
- return JSON.parse(localStorage.getItem('email_queue')) || {};
- } catch (e) {
- return {}
- }
- },
-
- get_customers_details: function () {
- try {
- return JSON.parse(localStorage.getItem('customer_details')) || {};
- } catch (e) {
- return {}
- }
- },
-
- edit_record: function () {
- var me = this;
-
- doc_data = this.get_invoice_doc(this.si_docs);
- if (doc_data) {
- this.frm.doc = doc_data[0][this.frm.doc.offline_pos_name];
- this.set_missing_values();
- this.refresh(false);
- this.toggle_input_field();
- this.list_dialog && this.list_dialog.hide();
- }
- },
-
- delete_records: function () {
- var me = this;
- this.validate_list()
- this.remove_doc_from_localstorage()
- this.update_localstorage();
- this.toggle_delete_button();
- },
-
- validate_list: function() {
- var me = this;
- this.si_docs = this.get_submitted_invoice()
- $.each(this.removed_items, function(index, pos_name){
- $.each(me.si_docs, function(key, data){
- if(me.si_docs[key][pos_name] && me.si_docs[key][pos_name].offline_pos_name == pos_name ){
- frappe.throw(__("Submitted orders can not be deleted"))
- }
- })
- })
- },
-
- toggle_delete_button: function () {
- var me = this;
- if(this.pos_profile_data["allow_delete"]) {
- if (this.removed_items && this.removed_items.length > 0) {
- $(this.page.wrapper).find('.btn-danger').show();
- } else {
- $(this.page.wrapper).find('.btn-danger').hide();
- }
- }
- },
-
- get_doctype_status: function (doc) {
- if (doc.docstatus == 0) {
- return { status: "Draft", indicator: "red" }
- } else if (doc.outstanding_amount == 0) {
- return { status: "Paid", indicator: "green" }
- } else {
- return { status: "Submitted", indicator: "blue" }
- }
- },
-
- set_missing_values: function () {
- var me = this;
- doc = JSON.parse(localStorage.getItem('doc'))
- if (this.frm.doc.payments.length == 0) {
- this.frm.doc.payments = doc.payments;
- this.calculate_outstanding_amount();
- }
-
- this.set_customer_value_in_party_field();
-
- if (!this.frm.doc.write_off_account) {
- this.frm.doc.write_off_account = doc.write_off_account
- }
-
- if (!this.frm.doc.account_for_change_amount) {
- this.frm.doc.account_for_change_amount = doc.account_for_change_amount
- }
- },
-
- set_customer_value_in_party_field: function() {
- if (this.frm.doc.customer) {
- this.party_field.$input.val(this.frm.doc.customer);
- }
- },
-
- get_invoice_doc: function (si_docs) {
- var me = this;
- this.si_docs = this.get_doc_from_localstorage();
-
- return $.grep(this.si_docs, function (data) {
- for (key in data) {
- return key == me.frm.doc.offline_pos_name;
- }
- })
- },
-
- get_data_from_server: function (callback) {
- var me = this;
- frappe.call({
- method: "erpnext.accounts.doctype.sales_invoice.pos.get_pos_data",
- freeze: true,
- freeze_message: __("Master data syncing, it might take some time"),
- callback: function (r) {
- localStorage.setItem('doc', JSON.stringify(r.message.doc));
- me.init_master_data(r)
- me.set_interval_for_si_sync();
- me.check_internet_connection();
- if (callback) {
- callback();
- }
- },
- error: () => {
- setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000);
- }
- })
- },
-
- init_master_data: function (r) {
- var me = this;
- this.doc = JSON.parse(localStorage.getItem('doc'));
- this.meta = r.message.meta;
- this.item_data = r.message.items;
- this.item_groups = r.message.item_groups;
- this.customers = r.message.customers;
- this.serial_no_data = r.message.serial_no_data;
- this.batch_no_data = r.message.batch_no_data;
- this.barcode_data = r.message.barcode_data;
- this.tax_data = r.message.tax_data;
- this.contacts = r.message.contacts;
- this.address = r.message.address || {};
- this.price_list_data = r.message.price_list_data;
- this.customer_wise_price_list = r.message.customer_wise_price_list
- this.bin_data = r.message.bin_data;
- this.pricing_rules = r.message.pricing_rules;
- this.print_template = r.message.print_template;
- this.pos_profile_data = r.message.pos_profile;
- this.default_customer = r.message.default_customer || null;
- this.print_settings = locals[":Print Settings"]["Print Settings"];
- this.letter_head = (this.pos_profile_data.length > 0) ? frappe.boot.letter_heads[this.pos_profile_data[letter_head]] : {};
- },
-
- save_previous_entry: function () {
- if (this.frm.doc.docstatus < 1 && this.frm.doc.items.length > 0) {
- this.create_invoice();
- }
- },
-
- create_new: function () {
- var me = this;
- this.frm = {}
- this.load_data(true);
- this.frm.doc.offline_pos_name = '';
- this.setup();
- this.set_default_customer()
- },
-
- load_data: function (load_doc) {
- var me = this;
-
- this.items = this.item_data;
- this.actual_qty_dict = {};
-
- if (load_doc) {
- this.frm.doc = JSON.parse(localStorage.getItem('doc'));
- }
-
- $.each(this.meta, function (i, data) {
- frappe.meta.sync(data)
- locals["DocType"][data.name] = data;
- })
-
- this.print_template_data = frappe.render_template("print_template", {
- content: this.print_template,
- title: "POS",
- base_url: frappe.urllib.get_base_url(),
- print_css: frappe.boot.print_css,
- print_settings: this.print_settings,
- header: this.letter_head.header,
- footer: this.letter_head.footer,
- landscape: false,
- columns: []
- })
- },
-
- setup: function () {
- this.set_primary_action();
- this.party_field.$input.attr('disabled', false);
- if(this.selected_row) {
- this.selected_row.hide()
- }
- },
-
- set_default_customer: function() {
- if (this.default_customer && !this.frm.doc.customer) {
- this.party_field.$input.val(this.default_customer);
- this.frm.doc.customer = this.default_customer;
- this.numeric_keypad.show();
- this.toggle_list_customer(false)
- this.toggle_item_cart(true)
- }
- },
-
- set_transaction_defaults: function (party) {
- var me = this;
- this.party = party;
- this.price_list = (party == "Customer" ?
- this.frm.doc.selling_price_list : this.frm.doc.buying_price_list);
- this.price_list_field = (party == "Customer" ? "selling_price_list" : "buying_price_list");
- this.sales_or_purchase = (party == "Customer" ? "Sales" : "Purchase");
- },
-
- make: function () {
- this.make_item_list();
- this.make_discount_field()
- },
-
- make_control: function() {
- this.frm = {}
- this.frm.doc = this.doc
- this.set_transaction_defaults("Customer");
- this.frm.doc["allow_user_to_edit_rate"] = this.pos_profile_data["allow_user_to_edit_rate"] ? true : false;
- this.frm.doc["allow_user_to_edit_discount"] = this.pos_profile_data["allow_user_to_edit_discount"] ? true : false;
- this.wrapper.html(frappe.render_template("pos", this.frm.doc));
- this.make_search();
- this.make_customer();
- this.make_list_customers();
- this.bind_numeric_keypad();
- },
-
- make_search: function () {
- var me = this;
- this.search_item = frappe.ui.form.make_control({
- df: {
- "fieldtype": "Data",
- "label": __("Item"),
- "fieldname": "pos_item",
- "placeholder": __("Search Item")
- },
- parent: this.wrapper.find(".search-item"),
- only_input: true,
- });
-
- this.search_item.make_input();
-
- this.search_item.$input.on("keypress", function (event) {
-
- clearTimeout(me.last_search_timeout);
- me.last_search_timeout = setTimeout(() => {
- if((me.search_item.$input.val() != "") || (event.which == 13)) {
- me.items = me.get_items();
- me.make_item_list();
- }
- }, 400);
- });
-
- this.search_item_group = this.wrapper.find('.search-item-group');
- sorted_item_groups = this.get_sorted_item_groups()
- var dropdown_html = sorted_item_groups.map(function(item_group) {
- return "<li><a class='option' data-value='"+item_group+"'>"+item_group+"</a></li>";
- }).join("");
-
- this.search_item_group.find('.dropdown-menu').html(dropdown_html);
-
- this.search_item_group.on('click', '.dropdown-menu a', function() {
- me.selected_item_group = $(this).attr('data-value');
- me.search_item_group.find('.dropdown-text').text(me.selected_item_group);
-
- me.page_len = 20;
- me.items = me.get_items();
- me.make_item_list();
- })
-
- me.toggle_more_btn();
-
- this.wrapper.on("click", ".btn-more", function() {
- me.page_len += 20;
- me.items = me.get_items();
- me.make_item_list();
- me.toggle_more_btn();
- });
-
- this.page.wrapper.on("click", ".edit-customer-btn", function() {
- me.update_customer()
- })
- },
-
- get_sorted_item_groups: function() {
- list = {}
- $.each(this.item_groups, function(i, data) {
- list[i] = data[0]
- })
-
- return Object.keys(list).sort(function(a,b){return list[a]-list[b]})
- },
-
- toggle_more_btn: function() {
- if(!this.items || this.items.length <= this.page_len) {
- this.wrapper.find(".btn-more").hide();
- } else {
- this.wrapper.find(".btn-more").show();
- }
- },
-
- toggle_totals_area: function(show) {
-
- if(show === undefined) {
- show = this.is_totals_area_collapsed;
- }
-
- var totals_area = this.wrapper.find('.totals-area');
- totals_area.find('.net-total-area, .tax-area, .discount-amount-area')
- .toggle(show);
-
- if(show) {
- totals_area.find('.collapse-btn i')
- .removeClass('octicon-chevron-down')
- .addClass('octicon-chevron-up');
- } else {
- totals_area.find('.collapse-btn i')
- .removeClass('octicon-chevron-up')
- .addClass('octicon-chevron-down');
- }
-
- this.is_totals_area_collapsed = !show;
- },
-
- make_list_customers: function () {
- var me = this;
- this.list_customers_btn = this.page.wrapper.find('.list-customers-btn');
- this.add_customer_btn = this.wrapper.find('.add-customer-btn');
- this.pos_bill = this.wrapper.find('.pos-bill-wrapper').hide();
- this.list_customers = this.wrapper.find('.list-customers');
- this.numeric_keypad = this.wrapper.find('.numeric_keypad');
- this.list_customers_btn.addClass("view_customer")
-
- me.render_list_customers();
- me.toggle_totals_area(false);
-
- this.page.wrapper.on('click', '.list-customers-btn', function() {
- $(this).toggleClass("view_customer");
- if($(this).hasClass("view_customer")) {
- me.render_list_customers();
- me.list_customers.show();
- me.pos_bill.hide();
- me.numeric_keypad.hide();
- me.toggle_delete_button()
- } else {
- if(me.frm.doc.docstatus == 0) {
- me.party_field.$input.attr('disabled', false);
- }
- me.pos_bill.show();
- me.toggle_totals_area(false);
- me.toggle_delete_button()
- me.list_customers.hide();
- me.numeric_keypad.show();
- }
- });
- this.add_customer_btn.on('click', function() {
- me.save_previous_entry();
- me.create_new();
- me.refresh();
- me.set_focus();
- });
- this.pos_bill.on('click', '.collapse-btn', function() {
- me.toggle_totals_area();
- });
- },
-
- bind_numeric_keypad: function() {
- var me = this;
- $(this.numeric_keypad).find('.pos-operation').on('click', function(){
- me.numeric_val = '';
- })
-
- $(this.numeric_keypad).find('.numeric-keypad').on('click', function(){
- me.numeric_id = $(this).attr("id") || me.numeric_id;
- me.val = $(this).attr("val")
- if(me.numeric_id) {
- me.selected_field = $(me.wrapper).find('.selected-item').find('.' + me.numeric_id)
- }
-
- if(me.val && me.numeric_id) {
- me.numeric_val += me.val;
- me.selected_field.val(flt(me.numeric_val))
- me.selected_field.trigger("change")
- // me.render_selected_item()
- }
-
- if(me.numeric_id && $(this).hasClass('pos-operation')) {
- me.numeric_keypad.find('button.pos-operation').removeClass('active');
- $(this).addClass('active');
-
- me.selected_row.find('.pos-list-row').removeClass('active');
- me.selected_field.closest('.pos-list-row').addClass('active');
- }
- })
-
- $(this.numeric_keypad).find('.numeric-del').click(function(){
- if(me.numeric_id) {
- me.selected_field = $(me.wrapper).find('.selected-item').find('.' + me.numeric_id)
- me.numeric_val = cstr(flt(me.selected_field.val())).slice(0, -1);
- me.selected_field.val(me.numeric_val);
- me.selected_field.trigger("change")
- } else {
- //Remove an item from the cart, if focus is at selected item
- me.remove_selected_item()
- }
- })
-
- $(this.numeric_keypad).find('.pos-pay').click(function(){
- me.validate();
- me.update_paid_amount_status(true);
- me.create_invoice();
- me.make_payment();
- })
- },
-
- remove_selected_item: function() {
- this.remove_item = []
- idx = $(this.wrapper).find(".pos-selected-item-action").attr("data-idx")
- this.remove_item.push(idx)
- this.remove_zero_qty_items_from_cart()
- this.update_paid_amount_status(false)
- },
-
- render_list_customers: function () {
- var me = this;
-
- this.removed_items = [];
- // this.list_customers.empty();
- this.si_docs = this.get_doc_from_localstorage();
- if (!this.si_docs.length) {
- this.list_customers.find('.list-customers-table').html("");
- return;
- }
-
- var html = "";
- if(this.si_docs.length) {
- this.si_docs.forEach(function (data, i) {
- for (var key in data) {
- html += frappe.render_template("pos_invoice_list", {
- sr: i + 1,
- name: key,
- customer: data[key].customer,
- paid_amount: format_currency(data[key].paid_amount, me.frm.doc.currency),
- grand_total: format_currency(data[key].grand_total, me.frm.doc.currency),
- data: me.get_doctype_status(data[key])
- });
- }
- });
- }
- this.list_customers.find('.list-customers-table').html(html);
-
- this.list_customers.on('click', '.customer-row', function () {
- me.list_customers.hide();
- me.numeric_keypad.show();
- me.list_customers_btn.toggleClass("view_customer");
- me.pos_bill.show();
- me.list_customers_btn.show();
- me.frm.doc.offline_pos_name = $(this).parents().attr('invoice-name');
- me.edit_record();
- })
-
- //actions
- $(this.wrapper).find('.list-select-all').click(function () {
- me.list_customers.find('.list-delete').prop("checked", $(this).is(":checked"))
- me.removed_items = [];
- if ($(this).is(":checked")) {
- $.each(me.si_docs, function (index, data) {
- for (key in data) {
- me.removed_items.push(key)
- }
- });
- }
-
- me.toggle_delete_button();
- });
-
- $(this.wrapper).find('.list-delete').click(function () {
- me.frm.doc.offline_pos_name = $(this).parent().parent().attr('invoice-name');
- if ($(this).is(":checked")) {
- me.removed_items.push(me.frm.doc.offline_pos_name);
- } else {
- me.removed_items.pop(me.frm.doc.offline_pos_name)
- }
-
- me.toggle_delete_button();
- });
- },
-
- bind_delete_event: function() {
- var me = this;
-
- $(this.page.wrapper).on('click', '.btn-danger', function(){
- frappe.confirm(__("Delete permanently?"), function () {
- me.delete_records();
- me.list_customers.find('.list-customers-table').html("");
- me.render_list_customers();
- })
- })
- },
-
- set_focus: function () {
- if (this.default_customer || this.frm.doc.customer) {
- this.set_customer_value_in_party_field();
- this.search_item.$input.focus();
- } else {
- this.party_field.$input.focus();
- }
- },
-
- make_customer: function () {
- var me = this;
-
- if(!this.party_field) {
- if(this.page.wrapper.find('.pos-bill-toolbar').length === 0) {
- $(frappe.render_template('customer_toolbar', {
- allow_delete: this.pos_profile_data["allow_delete"]
- })).insertAfter(this.page.$title_area.hide());
- }
-
- this.party_field = frappe.ui.form.make_control({
- df: {
- "fieldtype": "Data",
- "options": this.party,
- "label": this.party,
- "fieldname": this.party.toLowerCase(),
- "placeholder": __("Select or add new customer")
- },
- parent: this.page.wrapper.find(".party-area"),
- only_input: true,
- });
-
- this.party_field.make_input();
- setTimeout(this.set_focus.bind(this), 500);
- me.toggle_delete_button();
- }
-
- this.party_field.awesomeplete =
- new Awesomplete(this.party_field.$input.get(0), {
- minChars: 0,
- maxItems: 99,
- autoFirst: true,
- list: [],
- filter: function (item, input) {
- if (item.value.includes('is_action')) {
- return true;
- }
-
- input = input.toLowerCase();
- item = this.get_item(item.value);
- result = item ? item.searchtext.includes(input) : '';
- if(!result) {
- me.prepare_customer_mapper(input);
- } else {
- return result;
- }
- },
- item: function (item, input) {
- var d = this.get_item(item.value);
- var html = "<span>" + __(d.label || d.value) + "</span>";
- if(d.customer_name) {
- html += '<br><span class="text-muted ellipsis">' + __(d.customer_name) + '</span>';
- }
-
- return $('<li></li>')
- .data('item.autocomplete', d)
- .html('<a><p>' + html + '</p></a>')
- .get(0);
- }
- });
-
- this.prepare_customer_mapper()
- this.autocomplete_customers();
-
- this.party_field.$input
- .on('input', function (e) {
- if(me.customers_mapper.length <= 1) {
- me.prepare_customer_mapper(e.target.value);
- }
- me.party_field.awesomeplete.list = me.customers_mapper;
- })
- .on('awesomplete-select', function (e) {
- var customer = me.party_field.awesomeplete
- .get_item(e.originalEvent.text.value);
- if (!customer) return;
- // create customer link
- if (customer.action) {
- customer.action.apply(me);
- return;
- }
- me.toggle_list_customer(false);
- me.toggle_edit_button(true);
- me.update_customer_data(customer);
- me.refresh();
- me.set_focus();
- me.list_customers_btn.removeClass("view_customer");
- })
- .on('focus', function (e) {
- $(e.target).val('').trigger('input');
- me.toggle_edit_button(false);
-
- if(me.frm.doc.items.length) {
- me.toggle_list_customer(false)
- me.toggle_item_cart(true)
- } else {
- me.toggle_list_customer(true)
- me.toggle_item_cart(false)
- }
- })
- .on("awesomplete-selectcomplete", function (e) {
- var item = me.party_field.awesomeplete
- .get_item(e.originalEvent.text.value);
- // clear text input if item is action
- if (item.action) {
- $(this).val("");
- }
- me.make_item_list(item.customer_name);
- });
- },
-
- prepare_customer_mapper: function(key) {
- var me = this;
- var customer_data = '';
-
- if (key) {
- key = key.toLowerCase().trim();
- var re = new RegExp('%', 'g');
- var reg = new RegExp(key.replace(re, '\\w*\\s*[a-zA-Z0-9]*'));
-
- customer_data = $.grep(this.customers, function(data) {
- contact = me.contacts[data.name];
- if(reg.test(data.name.toLowerCase())
- || reg.test(data.customer_name.toLowerCase())
- || (contact && reg.test(contact["phone"]))
- || (contact && reg.test(contact["mobile_no"]))
- || (data.customer_group && reg.test(data.customer_group.toLowerCase()))){
- return data;
- }
- })
- } else {
- customer_data = this.customers;
- }
-
- this.customers_mapper = [];
-
- customer_data.forEach(function (c, index) {
- if(index < 30) {
- contact = me.contacts[c.name];
- if(contact && !c['phone']) {
- c["phone"] = contact["phone"];
- c["email_id"] = contact["email_id"];
- c["mobile_no"] = contact["mobile_no"];
- }
-
- me.customers_mapper.push({
- label: c.name,
- value: c.name,
- customer_name: c.customer_name,
- customer_group: c.customer_group,
- territory: c.territory,
- phone: contact ? contact["phone"] : '',
- mobile_no: contact ? contact["mobile_no"] : '',
- email_id: contact ? contact["email_id"] : '',
- searchtext: ['customer_name', 'customer_group', 'name', 'value',
- 'label', 'email_id', 'phone', 'mobile_no']
- .map(key => c[key]).join(' ')
- .toLowerCase()
- });
- } else {
- return;
- }
- });
-
- this.customers_mapper.push({
- label: "<span class='text-primary link-option'>"
- + "<i class='fa fa-plus' style='margin-right: 5px;'></i> "
- + __("Create a new Customer")
- + "</span>",
- value: 'is_action',
- action: me.add_customer
- });
- },
-
- autocomplete_customers: function() {
- this.party_field.awesomeplete.list = this.customers_mapper;
- },
-
- toggle_edit_button: function(flag) {
- this.page.wrapper.find('.edit-customer-btn').toggle(flag);
- },
-
- toggle_list_customer: function(flag) {
- this.list_customers.toggle(flag);
- },
-
- toggle_item_cart: function(flag) {
- this.wrapper.find('.pos-bill-wrapper').toggle(flag);
- },
-
- add_customer: function() {
- this.frm.doc.customer = "";
- this.update_customer(true);
- this.numeric_keypad.show();
- },
-
- update_customer: function (new_customer) {
- var me = this;
-
- this.customer_doc = new frappe.ui.Dialog({
- 'title': 'Customer',
- fields: [
- {
- "label": __("Full Name"),
- "fieldname": "full_name",
- "fieldtype": "Data",
- "reqd": 1
- },
- {
- "fieldtype": "Section Break"
- },
- {
- "label": __("Email Id"),
- "fieldname": "email_id",
- "fieldtype": "Data"
- },
- {
- "fieldtype": "Column Break"
- },
- {
- "label": __("Contact Number"),
- "fieldname": "phone",
- "fieldtype": "Data"
- },
- {
- "fieldtype": "Section Break"
- },
- {
- "label": __("Address Name"),
- "read_only": 1,
- "fieldname": "name",
- "fieldtype": "Data"
- },
- {
- "label": __("Address Line 1"),
- "fieldname": "address_line1",
- "fieldtype": "Data"
- },
- {
- "label": __("Address Line 2"),
- "fieldname": "address_line2",
- "fieldtype": "Data"
- },
- {
- "fieldtype": "Column Break"
- },
- {
- "label": __("City"),
- "fieldname": "city",
- "fieldtype": "Data"
- },
- {
- "label": __("State"),
- "fieldname": "state",
- "fieldtype": "Data"
- },
- {
- "label": __("ZIP Code"),
- "fieldname": "pincode",
- "fieldtype": "Data"
- },
- {
- "label": __("Customer POS Id"),
- "fieldname": "customer_pos_id",
- "fieldtype": "Data",
- "hidden": 1
- }
- ]
- })
- this.customer_doc.show()
- this.render_address_data()
-
- this.customer_doc.set_primary_action(__("Save"), function () {
- me.make_offline_customer(new_customer);
- me.pos_bill.show();
- me.list_customers.hide();
- });
- },
-
- render_address_data: function() {
- var me = this;
- this.address_data = this.address[this.frm.doc.customer] || {};
- if(!this.address_data.email_id || !this.address_data.phone) {
- this.address_data = this.contacts[this.frm.doc.customer];
- }
-
- this.customer_doc.set_values(this.address_data)
- if(!this.customer_doc.fields_dict.full_name.$input.val()) {
- this.customer_doc.set_value("full_name", this.frm.doc.customer)
- }
-
- if(!this.customer_doc.fields_dict.customer_pos_id.value) {
- this.customer_doc.set_value("customer_pos_id", frappe.datetime.now_datetime())
- }
- },
-
- get_address_from_localstorage: function() {
- this.address_details = this.get_customers_details()
- return this.address_details[this.frm.doc.customer]
- },
-
- make_offline_customer: function(new_customer) {
- this.frm.doc.customer = this.frm.doc.customer || this.customer_doc.get_values().full_name;
- this.frm.doc.customer_pos_id = this.customer_doc.fields_dict.customer_pos_id.value;
- this.customer_details = this.get_customers_details();
- this.customer_details[this.frm.doc.customer] = this.get_prompt_details();
- this.party_field.$input.val(this.frm.doc.customer);
- this.update_address_and_customer_list(new_customer)
- this.autocomplete_customers();
- this.update_customer_in_localstorage()
- this.update_customer_in_localstorage()
- this.customer_doc.hide()
- },
-
- update_address_and_customer_list: function(new_customer) {
- var me = this;
- if(new_customer) {
- this.customers_mapper.push({
- label: this.frm.doc.customer,
- value: this.frm.doc.customer,
- customer_group: "",
- territory: ""
- });
- }
-
- this.address[this.frm.doc.customer] = JSON.parse(this.get_prompt_details())
- },
-
- get_prompt_details: function() {
- this.prompt_details = this.customer_doc.get_values();
- this.prompt_details['country'] = this.pos_profile_data.country;
- this.prompt_details['territory'] = this.pos_profile_data["territory"];
- this.prompt_details['customer_group'] = this.pos_profile_data["customer_group"];
- this.prompt_details['customer_pos_id'] = this.customer_doc.fields_dict.customer_pos_id.value;
- return JSON.stringify(this.prompt_details)
- },
-
- update_customer_data: function (doc) {
- var me = this;
- this.frm.doc.customer = doc.label || doc.name;
- this.frm.doc.customer_name = doc.customer_name;
- this.frm.doc.customer_group = doc.customer_group;
- this.frm.doc.territory = doc.territory;
- this.pos_bill.show();
- this.numeric_keypad.show();
- },
-
- make_item_list: function (customer) {
- var me = this;
- if (!this.price_list) {
- frappe.msgprint(__("Price List not found or disabled"));
- return;
- }
-
- me.item_timeout = null;
-
- var $wrap = me.wrapper.find(".item-list");
- me.wrapper.find(".item-list").empty();
-
- if (this.items.length > 0) {
- $.each(this.items, function(index, obj) {
- let customer_price_list = me.customer_wise_price_list[customer];
- let item_price
- if (customer && customer_price_list && customer_price_list[obj.name]) {
- item_price = format_currency(customer_price_list[obj.name], me.frm.doc.currency);
- } else {
- item_price = format_currency(me.price_list_data[obj.name], me.frm.doc.currency);
- }
- if(index < me.page_len) {
- $(frappe.render_template("pos_item", {
- item_code: obj.name,
- item_price: item_price,
- item_name: obj.name === obj.item_name ? "" : obj.item_name,
- item_image: obj.image,
- item_stock: __('Stock Qty') + ": " + me.get_actual_qty(obj),
- item_uom: obj.stock_uom,
- color: frappe.get_palette(obj.item_name),
- abbr: frappe.get_abbr(obj.item_name)
- })).tooltip().appendTo($wrap);
- }
- });
-
- $wrap.append(`
- <div class="image-view-item btn-more text-muted text-center">
- <div class="image-view-body">
- <i class="mega-octicon octicon-package"></i>
- <div>Load more items</div>
- </div>
- </div>
- `);
-
- me.toggle_more_btn();
- } else {
- $("<p class='text-muted small' style='padding-left: 10px'>"
- +__("Not items found")+"</p>").appendTo($wrap)
- }
-
- if (this.items.length == 1
- && this.search_item.$input.val()) {
- this.search_item.$input.val("");
- this.add_to_cart();
- }
- },
-
- get_items: function (item_code) {
- // To search item as per the key enter
-
- var me = this;
- this.item_serial_no = {};
- this.item_batch_no = {};
-
- if (item_code) {
- return $.grep(this.item_data, function (item) {
- if (item.item_code == item_code) {
- return true
- }
- })
- }
-
- this.items_list = this.apply_category();
-
- key = this.search_item.$input.val().toLowerCase().replace(/[&\/\\#,+()\[\]$~.'":*?<>{}]/g, '\\$&');
- var re = new RegExp('%', 'g');
- var reg = new RegExp(key.replace(re, '[\\w*\\s*[a-zA-Z0-9]*]*'))
- search_status = true
-
- if (key) {
- return $.grep(this.items_list, function (item) {
- if (search_status) {
- if (me.batch_no_data[item.item_code] &&
- in_list(me.batch_no_data[item.item_code], me.search_item.$input.val())) {
- search_status = false;
- return me.item_batch_no[item.item_code] = me.search_item.$input.val()
- } else if (me.serial_no_data[item.item_code]
- && in_list(Object.keys(me.serial_no_data[item.item_code]), me.search_item.$input.val())) {
- search_status = false;
- me.item_serial_no[item.item_code] = [me.search_item.$input.val(), me.serial_no_data[item.item_code][me.search_item.$input.val()]]
- return true
- } else if (me.barcode_data[item.item_code] &&
- in_list(me.barcode_data[item.item_code], me.search_item.$input.val())) {
- search_status = false;
- return true;
- } else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) ||
- reg.test(item.item_name.toLowerCase()) || reg.test(item.item_group.toLowerCase())) {
- return true
- }
- }
- })
- } else {
- return this.items_list;
- }
- },
-
- apply_category: function() {
- var me = this;
- category = this.selected_item_group || "All Item Groups";
- if(category == 'All Item Groups') {
- return this.item_data
- } else {
- return this.item_data.filter(function(element, index, array){
- return element.item_group == category;
- });
- }
- },
-
- bind_items_event: function() {
- var me = this;
- $(this.wrapper).on('click', '.pos-bill-item', function() {
- $(me.wrapper).find('.pos-bill-item').removeClass('active');
- $(this).addClass('active');
- me.numeric_val = "";
- me.numeric_id = ""
- me.item_code = $(this).attr("data-item-code");
- me.render_selected_item()
- me.bind_qty_event()
- me.update_rate()
- $(me.wrapper).find(".selected-item").scrollTop(1000);
- })
- },
-
- bind_qty_event: function () {
- var me = this;
-
- $(this.wrapper).on("change", ".pos-item-qty", function () {
- var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code");
- var qty = $(this).val();
- me.update_qty(item_code, qty);
- me.update_value();
- })
-
- $(this.wrapper).on("focusout", ".pos-item-qty", function () {
- var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code");
- var qty = $(this).val();
- me.update_qty(item_code, qty, true);
- me.update_value();
- })
-
- $(this.wrapper).find("[data-action='increase-qty']").on("click", function () {
- var item_code = $(this).parents(".pos-bill-item").attr("data-item-code");
- var qty = flt($(this).parents(".pos-bill-item").find('.pos-item-qty').val()) + 1;
- me.update_qty(item_code, qty);
- })
-
- $(this.wrapper).find("[data-action='decrease-qty']").on("click", function () {
- var item_code = $(this).parents(".pos-bill-item").attr("data-item-code");
- var qty = flt($(this).parents(".pos-bill-item").find('.pos-item-qty').val()) - 1;
- me.update_qty(item_code, qty);
- })
-
- $(this.wrapper).on("change", ".pos-item-disc", function () {
- var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code");
- var discount = $(this).val();
- if(discount > 100){
- discount = $(this).val('');
- frappe.show_alert({
- indicator: 'red',
- message: __('Discount amount cannot be greater than 100%')
- });
- me.update_discount(item_code, discount);
- }else{
- me.update_discount(item_code, discount);
- me.update_value();
- }
- })
- },
-
- bind_events: function() {
- var me = this;
- // if form is local then allow this function
- // $(me.wrapper).find(".pos-item-wrapper").on("click", function () {
- $(this.wrapper).on("click", ".pos-item-wrapper", function () {
- me.item_code = '';
- me.customer_validate();
- if($(me.pos_bill).is(":hidden")) return;
-
- if (me.frm.doc.docstatus == 0) {
- me.items = me.get_items($(this).attr("data-item-code"))
- me.add_to_cart();
- me.clear_selected_row();
- }
- });
-
- me.bind_delete_event()
- },
-
- update_qty: function (item_code, qty, remove_zero_qty_items) {
- var me = this;
- this.items = this.get_items(item_code);
- this.validate_serial_no()
- this.set_item_details(item_code, "qty", qty, remove_zero_qty_items);
- },
-
- update_discount: function(item_code, discount) {
- var me = this;
- this.items = this.get_items(item_code);
- this.set_item_details(item_code, "discount_percentage", discount);
- },
-
- update_rate: function () {
- var me = this;
- $(this.wrapper).on("change", ".pos-item-price", function () {
- var item_code = $(this).parents(".pos-selected-item-action").attr("data-item-code");
- me.set_item_details(item_code, "rate", $(this).val());
- me.update_value()
- })
- },
-
- update_value: function() {
- var me = this;
- var fields = {qty: ".pos-item-qty", "discount_percentage": ".pos-item-disc",
- "rate": ".pos-item-price", "amount": ".pos-amount"}
- this.child_doc = this.get_child_item(this.item_code);
-
- if(me.child_doc.length) {
- $.each(fields, function(key, field) {
- $(me.selected_row).find(field).val(me.child_doc[0][key])
- })
- } else {
- this.clear_selected_row();
- }
- },
-
- clear_selected_row: function() {
- $(this.wrapper).find('.selected-item').empty();
- },
-
- render_selected_item: function() {
- this.child_doc = this.get_child_item(this.item_code);
- $(this.wrapper).find('.selected-item').empty();
- if(this.child_doc.length) {
- this.child_doc[0]["allow_user_to_edit_rate"] = this.pos_profile_data["allow_user_to_edit_rate"] ? true : false,
- this.child_doc[0]["allow_user_to_edit_discount"] = this.pos_profile_data["allow_user_to_edit_discount"] ? true : false;
- this.selected_row = $(frappe.render_template("pos_selected_item", this.child_doc[0]))
- $(this.wrapper).find('.selected-item').html(this.selected_row)
- }
-
- $(this.selected_row).find('.form-control').click(function(){
- $(this).select();
- })
- },
-
- get_child_item: function(item_code) {
- var me = this;
- return $.map(me.frm.doc.items, function(doc){
- if(doc.item_code == item_code) {
- return doc
- }
- })
- },
-
- set_item_details: function (item_code, field, value, remove_zero_qty_items) {
- var me = this;
- if (value < 0) {
- frappe.throw(__("Enter value must be positive"));
- }
-
- this.remove_item = []
- $.each(this.frm.doc["items"] || [], function (i, d) {
- if (d.item_code == item_code) {
- if (d.serial_no && field == 'qty') {
- me.validate_serial_no_qty(d, item_code, field, value)
- }
-
- d[field] = flt(value);
- d.amount = flt(d.rate) * flt(d.qty);
- if (d.qty == 0 && remove_zero_qty_items) {
- me.remove_item.push(d.idx)
- }
-
- if(field=="discount_percentage" && value == 0) {
- d.rate = d.price_list_rate;
- }
- }
- });
-
- if (field == 'qty') {
- this.remove_zero_qty_items_from_cart();
- }
-
- this.update_paid_amount_status(false)
- },
-
- remove_zero_qty_items_from_cart: function () {
- var me = this;
- var idx = 0;
- this.items = []
- $.each(this.frm.doc["items"] || [], function (i, d) {
- if (!in_list(me.remove_item, d.idx)) {
- d.idx = idx;
- me.items.push(d);
- idx++;
- }
- });
-
- this.frm.doc["items"] = this.items;
- },
-
- make_discount_field: function () {
- var me = this;
-
- this.wrapper.find('input.discount-percentage').on("change", function () {
- me.frm.doc.additional_discount_percentage = flt($(this).val(), precision("additional_discount_percentage"));
-
- if(me.frm.doc.additional_discount_percentage && me.frm.doc.discount_amount) {
- // Reset discount amount
- me.frm.doc.discount_amount = 0;
- }
-
- var total = me.frm.doc.grand_total
-
- if (me.frm.doc.apply_discount_on == 'Net Total') {
- total = me.frm.doc.net_total
- }
-
- me.frm.doc.discount_amount = flt(total * flt(me.frm.doc.additional_discount_percentage) / 100, precision("discount_amount"));
- me.refresh();
- me.wrapper.find('input.discount-amount').val(me.frm.doc.discount_amount)
- });
-
- this.wrapper.find('input.discount-amount').on("change", function () {
- me.frm.doc.discount_amount = flt($(this).val(), precision("discount_amount"));
- me.frm.doc.additional_discount_percentage = 0.0;
- me.refresh();
- me.wrapper.find('input.discount-percentage').val(0);
- });
- },
-
- customer_validate: function () {
- var me = this;
- if (!this.frm.doc.customer || this.party_field.get_value() == "") {
- frappe.throw(__("Please select customer"))
- }
- },
-
- add_to_cart: function () {
- var me = this;
- var caught = false;
- var no_of_items = me.wrapper.find(".pos-bill-item").length;
-
- this.customer_validate();
- this.mandatory_batch_no();
- this.validate_serial_no();
- this.validate_warehouse();
-
- if (no_of_items != 0) {
- $.each(this.frm.doc["items"] || [], function (i, d) {
- if (d.item_code == me.items[0].item_code) {
- caught = true;
- d.qty += 1;
- d.amount = flt(d.rate) * flt(d.qty);
- if (me.item_serial_no[d.item_code]) {
- d.serial_no += '\n' + me.item_serial_no[d.item_code][0]
- d.warehouse = me.item_serial_no[d.item_code][1]
- }
-
- if (me.item_batch_no.length) {
- d.batch_no = me.item_batch_no[d.item_code]
- }
- }
- });
- }
-
- // if item not found then add new item
- if (!caught)
- this.add_new_item_to_grid();
-
- this.update_paid_amount_status(false)
- this.wrapper.find(".item-cart-items").scrollTop(1000);
- },
-
- add_new_item_to_grid: function () {
- var me = this;
- this.child = frappe.model.add_child(this.frm.doc, this.frm.doc.doctype + " Item", "items");
- this.child.item_code = this.items[0].item_code;
- this.child.item_name = this.items[0].item_name;
- this.child.stock_uom = this.items[0].stock_uom;
- this.child.uom = this.items[0].sales_uom || this.items[0].stock_uom;
- this.child.conversion_factor = this.items[0].conversion_factor || 1;
- this.child.brand = this.items[0].brand;
- this.child.description = this.items[0].description || this.items[0].item_name;
- this.child.discount_percentage = 0.0;
- this.child.qty = 1;
- this.child.item_group = this.items[0].item_group;
- this.child.cost_center = this.pos_profile_data['cost_center'] || this.items[0].cost_center;
- this.child.income_account = this.pos_profile_data['income_account'] || this.items[0].income_account;
- this.child.warehouse = (this.item_serial_no[this.child.item_code]
- ? this.item_serial_no[this.child.item_code][1] : (this.pos_profile_data['warehouse'] || this.items[0].default_warehouse));
-
- customer = this.frm.doc.customer;
- let rate;
-
- customer_price_list = this.customer_wise_price_list[customer]
- if (customer_price_list && customer_price_list[this.child.item_code]){
- rate = flt(this.customer_wise_price_list[customer][this.child.item_code] * this.child.conversion_factor, 9) / flt(this.frm.doc.conversion_rate, 9);
- }
- else{
- rate = flt(this.price_list_data[this.child.item_code] * this.child.conversion_factor, 9) / flt(this.frm.doc.conversion_rate, 9);
- }
-
- this.child.price_list_rate = rate;
- this.child.rate = rate;
- this.child.actual_qty = me.get_actual_qty(this.items[0]);
- this.child.amount = flt(this.child.qty) * flt(this.child.rate);
- this.child.batch_no = this.item_batch_no[this.child.item_code];
- this.child.serial_no = (this.item_serial_no[this.child.item_code]
- ? this.item_serial_no[this.child.item_code][0] : '');
- this.child.item_tax_rate = JSON.stringify(this.tax_data[this.child.item_code]);
- },
-
- update_paid_amount_status: function (update_paid_amount) {
- if (this.frm.doc.offline_pos_name) {
- update_paid_amount = update_paid_amount ? false : true;
- }
-
- this.refresh(update_paid_amount);
- },
-
- refresh: function (update_paid_amount) {
- var me = this;
- this.refresh_fields(update_paid_amount);
- this.set_primary_action();
- },
-
- refresh_fields: function (update_paid_amount) {
- this.apply_pricing_rule();
- this.discount_amount_applied = false;
- this._calculate_taxes_and_totals();
- this.calculate_discount_amount();
- this.show_items_in_item_cart();
- this.set_taxes();
- this.calculate_outstanding_amount(update_paid_amount);
- this.set_totals();
- this.update_total_qty();
- },
-
- get_company_currency: function () {
- return erpnext.get_currency(this.frm.doc.company);
- },
-
- show_items_in_item_cart: function () {
- var me = this;
- var $items = this.wrapper.find(".items").empty();
- var $no_items_message = this.wrapper.find(".no-items-message");
- $no_items_message.toggle(this.frm.doc.items.length === 0);
-
- var $totals_area = this.wrapper.find('.totals-area');
- $totals_area.toggle(this.frm.doc.items.length > 0);
-
- $.each(this.frm.doc.items || [], function (i, d) {
- $(frappe.render_template("pos_bill_item_new", {
- item_code: d.item_code,
- item_name: (d.item_name === d.item_code || !d.item_name) ? "" : ("<br>" + d.item_name),
- qty: d.qty,
- discount_percentage: d.discount_percentage || 0.0,
- actual_qty: me.actual_qty_dict[d.item_code] || 0.0,
- projected_qty: d.projected_qty,
- rate: format_currency(d.rate, me.frm.doc.currency),
- amount: format_currency(d.amount, me.frm.doc.currency),
- selected_class: (me.item_code == d.item_code) ? "active" : ""
- })).appendTo($items);
- });
-
- this.wrapper.find("input.pos-item-qty").on("focus", function () {
- $(this).select();
- });
-
- this.wrapper.find("input.pos-item-disc").on("focus", function () {
- $(this).select();
- });
-
- this.wrapper.find("input.pos-item-price").on("focus", function () {
- $(this).select();
- });
- },
-
- set_taxes: function () {
- var me = this;
- me.frm.doc.total_taxes_and_charges = 0.0
-
- var taxes = this.frm.doc.taxes || [];
- $(this.wrapper)
- .find(".tax-area").toggleClass("hide", (taxes && taxes.length) ? false : true)
- .find(".tax-table").empty();
-
- $.each(taxes, function (i, d) {
- if (d.tax_amount && cint(d.included_in_print_rate) == 0) {
- $(frappe.render_template("pos_tax_row", {
- description: d.description,
- tax_amount: format_currency(flt(d.tax_amount_after_discount_amount),
- me.frm.doc.currency)
- })).appendTo(me.wrapper.find(".tax-table"));
- }
- });
- },
-
- set_totals: function () {
- var me = this;
- this.wrapper.find(".net-total").text(format_currency(me.frm.doc.total, me.frm.doc.currency));
- this.wrapper.find(".grand-total").text(format_currency(me.frm.doc.grand_total, me.frm.doc.currency));
- this.wrapper.find('input.discount-percentage').val(this.frm.doc.additional_discount_percentage);
- this.wrapper.find('input.discount-amount').val(this.frm.doc.discount_amount);
- },
-
- update_total_qty: function() {
- var me = this;
- var qty_total = 0;
- $.each(this.frm.doc["items"] || [], function (i, d) {
- if (d.item_code) {
- qty_total += d.qty;
- }
- });
- this.frm.doc.qty_total = qty_total;
- this.wrapper.find('.qty-total').text(this.frm.doc.qty_total);
- },
-
- set_primary_action: function () {
- var me = this;
- this.page.set_primary_action(__("New Cart"), function () {
- me.make_new_cart()
- me.make_menu_list()
- }, "fa fa-plus")
-
- if (this.frm.doc.docstatus == 1 || this.pos_profile_data["allow_print_before_pay"]) {
- this.page.set_secondary_action(__("Print"), function () {
- me.create_invoice();
- var html = frappe.render(me.print_template_data, me.frm.doc)
- me.print_document(html)
- })
- }
-
- if (this.frm.doc.docstatus == 1) {
- this.page.add_menu_item(__("Email"), function () {
- me.email_prompt()
- })
- }
- },
-
- make_new_cart: function (){
- this.item_code = '';
- this.page.clear_secondary_action();
- this.save_previous_entry();
- this.create_new();
- this.refresh();
- this.toggle_input_field();
- this.render_list_customers();
- this.set_focus();
- },
-
- print_dialog: function () {
- var me = this;
-
- this.msgprint = frappe.msgprint(
- `<a class="btn btn-primary print_doc"
- style="margin-right: 5px;">${__('Print')}</a>
- <a class="btn btn-default new_doc">${__('New')}</a>`);
-
- this.msgprint.msg_area.find('.print_doc').on('click', function() {
- var html = frappe.render(me.print_template_data, me.frm.doc);
- me.print_document(html);
- })
-
- this.msgprint.msg_area.find('.new_doc').on('click', function() {
- me.msgprint.hide();
- me.make_new_cart();
- })
-
- },
-
- print_document: function (html) {
- var w = window.open();
- w.document.write(html);
- w.document.close();
- setTimeout(function () {
- w.print();
- w.close();
- }, 1000);
- },
-
- submit_invoice: function () {
- var me = this;
- this.change_status();
- this.update_serial_no()
- if (this.frm.doc.docstatus == 1) {
- this.print_dialog()
- }
- },
-
- update_serial_no: function() {
- var me = this;
-
- //Remove the sold serial no from the cache
- $.each(this.frm.doc.items, function(index, data) {
- var sn = data.serial_no.split('\n')
- if(sn.length) {
- var serial_no_list = me.serial_no_data[data.item_code]
- if(serial_no_list) {
- $.each(sn, function(i, serial_no) {
- if(in_list(Object.keys(serial_no_list), serial_no)) {
- delete serial_no_list[serial_no]
- }
- })
- me.serial_no_data[data.item_code] = serial_no_list;
- }
- }
- })
- },
-
- change_status: function () {
- if (this.frm.doc.docstatus == 0) {
- this.frm.doc.docstatus = 1;
- this.update_invoice();
- this.toggle_input_field();
- }
- },
-
- toggle_input_field: function () {
- var pointer_events = 'inherit'
- var disabled = this.frm.doc.docstatus == 1 ? true: false;
- $(this.wrapper).find('input').attr("disabled", disabled);
- $(this.wrapper).find('select').attr("disabled", disabled);
- $(this.wrapper).find('input').attr("disabled", disabled);
- $(this.wrapper).find('select').attr("disabled", disabled);
- $(this.wrapper).find('button').attr("disabled", disabled);
- this.party_field.$input.attr('disabled', disabled);
-
- if (this.frm.doc.docstatus == 1) {
- pointer_events = 'none';
- }
-
- $(this.wrapper).find('.pos-bill').css('pointer-events', pointer_events);
- $(this.wrapper).find('.pos-items-section').css('pointer-events', pointer_events);
- this.set_primary_action();
-
- $(this.wrapper).find('#pos-item-disc').prop('disabled',
- this.pos_profile_data.allow_user_to_edit_discount ? false : true);
-
- $(this.wrapper).find('#pos-item-price').prop('disabled',
- this.pos_profile_data.allow_user_to_edit_rate ? false : true);
- },
-
- create_invoice: function () {
- var me = this;
- var existing_pos_list = [];
- var invoice_data = {};
- this.si_docs = this.get_doc_from_localstorage();
-
- if(this.si_docs) {
- this.si_docs.forEach((row) => {
- existing_pos_list.push(Object.keys(row)[0]);
- });
- }
-
- if (this.frm.doc.offline_pos_name
- && in_list(existing_pos_list, cstr(this.frm.doc.offline_pos_name))) {
- this.update_invoice()
- } else if(!this.frm.doc.offline_pos_name) {
- this.frm.doc.offline_pos_name = frappe.datetime.now_datetime();
- this.frm.doc.posting_date = frappe.datetime.get_today();
- this.frm.doc.posting_time = frappe.datetime.now_time();
- this.frm.doc.pos_total_qty = this.frm.doc.qty_total;
- this.frm.doc.pos_profile = this.pos_profile_data['name'];
- invoice_data[this.frm.doc.offline_pos_name] = this.frm.doc;
- this.si_docs.push(invoice_data);
- this.update_localstorage();
- this.set_primary_action();
- }
- return invoice_data;
- },
-
- update_invoice: function () {
- var me = this;
- this.si_docs = this.get_doc_from_localstorage();
- $.each(this.si_docs, function (index, data) {
- for (var key in data) {
- if (key == me.frm.doc.offline_pos_name) {
- me.si_docs[index][key] = me.frm.doc;
- me.update_localstorage();
- }
- }
- });
- },
-
- update_localstorage: function () {
- try {
- localStorage.setItem('sales_invoice_doc', JSON.stringify(this.si_docs));
- } catch (e) {
- frappe.throw(__("LocalStorage is full , did not save"))
- }
- },
-
- get_doc_from_localstorage: function () {
- try {
- return JSON.parse(localStorage.getItem('sales_invoice_doc')) || [];
- } catch (e) {
- return []
- }
- },
-
- set_interval_for_si_sync: function () {
- var me = this;
- setInterval(function () {
- me.freeze_screen = false;
- me.sync_sales_invoice()
- }, 180000)
- },
-
- sync_sales_invoice: function () {
- var me = this;
- this.si_docs = this.get_submitted_invoice() || [];
- this.email_queue_list = this.get_email_queue() || {};
- this.customers_list = this.get_customers_details() || {};
-
- if (this.si_docs.length || this.email_queue_list || this.customers_list) {
- frappe.call({
- method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice",
- freeze: true,
- args: {
- pos_profile: me.pos_profile_data,
- doc_list: me.si_docs,
- email_queue_list: me.email_queue_list,
- customers_list: me.customers_list
- },
- callback: function (r) {
- if (r.message) {
- me.freeze = false;
- me.customers = r.message.synced_customers_list;
- me.address = r.message.synced_address;
- me.contacts = r.message.synced_contacts;
- me.removed_items = r.message.invoice;
- me.removed_email = r.message.email_queue;
- me.removed_customers = r.message.customers;
- me.remove_doc_from_localstorage();
- me.remove_email_queue_from_localstorage();
- me.remove_customer_from_localstorage();
- me.prepare_customer_mapper();
- me.autocomplete_customers();
- me.render_list_customers();
- }
- }
- })
- }
- },
-
- get_submitted_invoice: function () {
- var invoices = [];
- var index = 1;
- var docs = this.get_doc_from_localstorage();
- if (docs) {
- invoices = $.map(docs, function (data) {
- for (var key in data) {
- if (data[key].docstatus == 1 && index < 50) {
- index++
- data[key].docstatus = 0;
- return data
- }
- }
- });
- }
-
- return invoices
- },
-
- remove_doc_from_localstorage: function () {
- var me = this;
- this.si_docs = this.get_doc_from_localstorage();
- this.new_si_docs = [];
- if (this.removed_items) {
- $.each(this.si_docs, function (index, data) {
- for (var key in data) {
- if (!in_list(me.removed_items, key)) {
- me.new_si_docs.push(data);
- }
- }
- })
- this.removed_items = [];
- this.si_docs = this.new_si_docs;
- this.update_localstorage();
- }
- },
-
- remove_email_queue_from_localstorage: function() {
- var me = this;
- this.email_queue = this.get_email_queue()
- if (this.removed_email) {
- $.each(this.email_queue_list, function (index, data) {
- if (in_list(me.removed_email, index)) {
- delete me.email_queue[index]
- }
- })
- this.update_email_queue();
- }
- },
-
- remove_customer_from_localstorage: function() {
- var me = this;
- this.customer_details = this.get_customers_details()
- if (this.removed_customers) {
- $.each(this.customers_list, function (index, data) {
- if (in_list(me.removed_customers, index)) {
- delete me.customer_details[index]
- }
- })
- this.update_customer_in_localstorage();
- }
- },
-
- validate: function () {
- var me = this;
- this.customer_validate();
- this.validate_zero_qty_items();
- this.item_validate();
- this.validate_mode_of_payments();
- },
-
- validate_zero_qty_items: function() {
- this.remove_item = [];
-
- this.frm.doc.items.forEach(d => {
- if (d.qty == 0) {
- this.remove_item.push(d.idx);
- }
- });
-
- if(this.remove_item) {
- this.remove_zero_qty_items_from_cart();
- }
- },
-
- item_validate: function () {
- if (this.frm.doc.items.length == 0) {
- frappe.throw(__("Select items to save the invoice"))
- }
- },
-
- validate_mode_of_payments: function () {
- if (this.frm.doc.payments.length === 0) {
- frappe.throw(__("Payment Mode is not configured. Please check, whether account has been set on Mode of Payments or on POS Profile."))
- }
- },
-
- validate_serial_no: function () {
- var me = this;
- var item_code = ''
- var serial_no = '';
- for (var key in this.item_serial_no) {
- item_code = key;
- serial_no = me.item_serial_no[key][0];
- }
-
- if (this.items && this.items[0].has_serial_no && serial_no == "") {
- this.refresh();
- frappe.throw(__(repl("Error: Serial no is mandatory for item %(item)s", {
- 'item': this.items[0].item_code
- })))
- }
-
- if (item_code && serial_no) {
- $.each(this.frm.doc.items, function (index, data) {
- if (data.item_code == item_code) {
- if (in_list(data.serial_no.split('\n'), serial_no)) {
- frappe.throw(__(repl("Serial no %(serial_no)s is already taken", {
- 'serial_no': serial_no
- })))
- }
- }
- })
- }
- },
-
- validate_serial_no_qty: function (args, item_code, field, value) {
- var me = this;
- if (args.item_code == item_code && args.serial_no
- && field == 'qty' && cint(value) != value) {
- args.qty = 0.0;
- this.refresh();
- frappe.throw(__("Serial no item cannot be a fraction"))
- }
-
- if (args.item_code == item_code && args.serial_no && args.serial_no.split('\n').length != cint(value)) {
- args.qty = 0.0;
- args.serial_no = ''
- this.refresh();
- frappe.throw(__(repl("Total nos of serial no is not equal to quantity for item %(item)s.", {
- 'item': item_code
- })))
- }
- },
-
- mandatory_batch_no: function () {
- var me = this;
- if (this.items[0].has_batch_no && !this.item_batch_no[this.items[0].item_code]) {
- frappe.prompt([{
- 'fieldname': 'batch',
- 'fieldtype': 'Select',
- 'label': __('Batch No'),
- 'reqd': 1,
- 'options': this.batch_no_data[this.items[0].item_code]
- }],
- function(values){
- me.item_batch_no[me.items[0].item_code] = values.batch;
- const item = me.frm.doc.items.find(
- ({ item_code }) => item_code === me.items[0].item_code
- );
- if (item) {
- item.batch_no = values.batch;
- }
- },
- __('Select Batch No'))
- }
- },
-
- apply_pricing_rule: function () {
- var me = this;
- $.each(this.frm.doc["items"], function (n, item) {
- var pricing_rule = me.get_pricing_rule(item)
- me.validate_pricing_rule(pricing_rule)
- if (pricing_rule.length) {
- item.pricing_rule = pricing_rule[0].name;
- item.margin_type = pricing_rule[0].margin_type;
- item.price_list_rate = pricing_rule[0].price || item.price_list_rate;
- item.margin_rate_or_amount = pricing_rule[0].margin_rate_or_amount;
- item.discount_percentage = pricing_rule[0].discount_percentage || 0.0;
- me.apply_pricing_rule_on_item(item)
- } else if (item.pricing_rule) {
- item.price_list_rate = me.price_list_data[item.item_code]
- item.margin_rate_or_amount = 0.0;
- item.discount_percentage = 0.0;
- item.pricing_rule = null;
- me.apply_pricing_rule_on_item(item)
- }
-
- if(item.discount_percentage > 0) {
- me.apply_pricing_rule_on_item(item)
- }
- })
- },
-
- get_pricing_rule: function (item) {
- var me = this;
- return $.grep(this.pricing_rules, function (data) {
- if (item.qty >= data.min_qty && (item.qty <= (data.max_qty ? data.max_qty : item.qty))) {
- if (me.validate_item_condition(data, item)) {
- if (in_list(['Customer', 'Customer Group', 'Territory', 'Campaign'], data.applicable_for)) {
- return me.validate_condition(data)
- } else {
- return true
- }
- }
- }
- })
- },
-
- validate_item_condition: function (data, item) {
- var apply_on = frappe.model.scrub(data.apply_on);
-
- return (data.apply_on == 'Item Group')
- ? this.validate_item_group(data.item_group, item.item_group) : (data[apply_on] == item[apply_on]);
- },
-
- validate_item_group: function (pr_item_group, cart_item_group) {
- //pr_item_group = pricing rule's item group
- //cart_item_group = cart item's item group
- //this.item_groups has information about item group's lft and rgt
- //for example: {'Foods': [12, 19]}
-
- pr_item_group = this.item_groups[pr_item_group]
- cart_item_group = this.item_groups[cart_item_group]
-
- return (cart_item_group[0] >= pr_item_group[0] &&
- cart_item_group[1] <= pr_item_group[1])
- },
-
- validate_condition: function (data) {
- //This method check condition based on applicable for
- var condition = this.get_mapper_for_pricing_rule(data)[data.applicable_for]
- if (in_list(condition[1], condition[0])) {
- return true
- }
- },
-
- get_mapper_for_pricing_rule: function (data) {
- return {
- 'Customer': [data.customer, [this.frm.doc.customer]],
- 'Customer Group': [data.customer_group, [this.frm.doc.customer_group, 'All Customer Groups']],
- 'Territory': [data.territory, [this.frm.doc.territory, 'All Territories']],
- 'Campaign': [data.campaign, [this.frm.doc.campaign]],
- }
- },
-
- validate_pricing_rule: function (pricing_rule) {
- //This method validate duplicate pricing rule
- var pricing_rule_name = '';
- var priority = 0;
- var pricing_rule_list = [];
- var priority_list = []
-
- if (pricing_rule.length > 1) {
-
- $.each(pricing_rule, function (index, data) {
- pricing_rule_name += data.name + ','
- priority_list.push(data.priority)
- if (priority <= data.priority) {
- priority = data.priority
- pricing_rule_list.push(data)
- }
- })
-
- var count = 0
- $.each(priority_list, function (index, value) {
- if (value == priority) {
- count++
- }
- })
-
- if (priority == 0 || count > 1) {
- frappe.throw(__(repl("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: %(pricing_rule)s", {
- 'pricing_rule': pricing_rule_name
- })))
- }
-
- return pricing_rule_list
- }
- },
-
- validate_warehouse: function () {
- if (this.items[0].is_stock_item && !this.items[0].default_warehouse && !this.pos_profile_data['warehouse']) {
- frappe.throw(__("Default warehouse is required for selected item"))
- }
- },
-
- get_actual_qty: function (item) {
- this.actual_qty = 0.0;
-
- var warehouse = this.pos_profile_data['warehouse'] || item.default_warehouse;
- if (warehouse && this.bin_data[item.item_code]) {
- this.actual_qty = this.bin_data[item.item_code][warehouse] || 0;
- this.actual_qty_dict[item.item_code] = this.actual_qty
- }
-
- return this.actual_qty
- },
-
- update_customer_in_localstorage: function() {
- var me = this;
- try {
- localStorage.setItem('customer_details', JSON.stringify(this.customer_details));
- } catch (e) {
- frappe.throw(__("LocalStorage is full , did not save"))
- }
- }
-})
\ No newline at end of file
diff --git a/erpnext/accounts/page/pos/pos.json b/erpnext/accounts/page/pos/pos.json
deleted file mode 100644
index abd918a..0000000
--- a/erpnext/accounts/page/pos/pos.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "content": null,
- "creation": "2014-08-08 02:45:55.931022",
- "docstatus": 0,
- "doctype": "Page",
- "icon": "fa fa-th",
- "modified": "2014-08-08 05:59:33.045012",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "pos",
- "owner": "Administrator",
- "page_name": "pos",
- "roles": [
- {
- "role": "Sales User"
- },
- {
- "role": "Purchase User"
- },
- {
- "role": "Accounts User"
- }
- ],
- "script": null,
- "standard": "Yes",
- "style": null,
- "title": "POS"
-}
\ No newline at end of file
diff --git a/erpnext/accounts/page/pos/test_pos.js b/erpnext/accounts/page/pos/test_pos.js
deleted file mode 100644
index e5524a2..0000000
--- a/erpnext/accounts/page/pos/test_pos.js
+++ /dev/null
@@ -1,52 +0,0 @@
-QUnit.test("test:Sales Invoice", function(assert) {
- assert.expect(3);
- let done = assert.async();
-
- frappe.run_serially([
- () => {
- return frappe.tests.make("POS Profile", [
- {naming_series: "SINV"},
- {pos_profile_name: "_Test POS Profile"},
- {country: "India"},
- {currency: "INR"},
- {write_off_account: "Write Off - FT"},
- {write_off_cost_center: "Main - FT"},
- {payments: [
- [
- {"default": 1},
- {"mode_of_payment": "Cash"}
- ]]
- }
- ]);
- },
- () => cur_frm.save(),
- () => frappe.timeout(2),
- () => {
- assert.equal(cur_frm.doc.payments[0].default, 1, "Default mode of payment tested");
- },
- () => frappe.timeout(1),
- () => {
- return frappe.tests.make("Sales Invoice", [
- {customer: "Test Customer 2"},
- {is_pos: 1},
- {posting_date: frappe.datetime.get_today()},
- {due_date: frappe.datetime.get_today()},
- {items: [
- [
- {"item_code": "Test Product 1"},
- {"qty": 5},
- {"warehouse":'Stores - FT'}
- ]]
- }
- ]);
- },
- () => frappe.timeout(2),
- () => cur_frm.save(),
- () => frappe.timeout(2),
- () => {
- assert.equal(cur_frm.doc.payments[0].default, 1, "Default mode of payment tested");
- assert.equal(cur_frm.doc.payments[0].mode_of_payment, "Cash", "Default mode of payment tested");
- },
- () => done()
- ]);
-});
\ No newline at end of file
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index db91b66..28a6519 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -184,7 +184,7 @@
def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype):
- if doctype not in ["Sales Invoice", "Purchase Invoice"]:
+ if doctype not in ["POS Invoice", "Sales Invoice", "Purchase Invoice"]:
# not an invoice
return {
party_type.lower(): party
@@ -388,7 +388,7 @@
from erpnext.accounts.doctype.tax_rule.tax_rule import get_tax_template, get_party_details
args = {
party_type.lower(): party,
- "company": company
+ "company": company
}
if tax_category:
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/accounts/print_format/dunning_letter/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/accounts/print_format/dunning_letter/__init__.py
diff --git a/erpnext/accounts/print_format/dunning_letter/dunning_letter.json b/erpnext/accounts/print_format/dunning_letter/dunning_letter.json
new file mode 100644
index 0000000..a7eac70
--- /dev/null
+++ b/erpnext/accounts/print_format/dunning_letter/dunning_letter.json
@@ -0,0 +1,25 @@
+{
+ "align_labels_right": 0,
+ "creation": "2019-12-11 04:37:14.012805",
+ "css": ".print-format th {\n background-color: transparent !important;\n border-bottom: 1px solid !important;\n border-top: none !important;\n}\n.print-format .ql-editor {\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.print-format table {\n margin-bottom: 0px;\n }\n.print-format .table-data tr:last-child { \n border-bottom: 1px solid !important;\n}\n\n.print-format .table-inner tr:last-child {\n border-bottom:none !important;\n}\n.print-format .table-inner {\n margin: 0px 0px;\n}\n\n.print-format .table-data ul li { \n color:#787878 !important;\n}\n\n.no-top-border {\n border-top:none !important;\n}\n\n.table-inner td {\n padding-left: 0px !important; \n padding-top: 1px !important;\n padding-bottom: 1px !important;\n color:#787878 !important;\n}\n\n.total {\n background-color: lightgrey !important;\n padding-top: 4px !important;\n padding-bottom: 4px !important;\n}\n",
+ "custom_format": 0,
+ "default_print_language": "en",
+ "disabled": 0,
+ "doc_type": "Dunning",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Arial",
+ "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<b>{{doc.customer_name}}</b> <br />\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<div style=\\\"text-align:left;\\\">\\n<div style=\\\"font-size:24px; text-transform:uppercase;\\\">{{_(doc.dunning_type)}}</div>\\n<div style=\\\"font-size:16px;padding-bottom:5px;\\\">{{ doc.name }}</div>\\n</div>\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldname\": \"sales_invoice\", \"print_hide\": 0, \"label\": \"Sales Invoice\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"label\": \"Due Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<table class=\\\"table table-borderless table-data\\\">\\n <tbody>\\n <tr>\\n <th>{{_(\\\"Description\\\")}}</th>\\n\\t <th style=\\\"text-align: right;\\\">{{_(\\\"Amount\\\")}}</th>\\n </tr>\\n <tr>\\n <td>\\n {{_(\\\"Outstanding Amount\\\")}}\\n </td>\\n <td style=\\\"text-align: right;\\\">\\n {{doc.get_formatted(\\\"outstanding_amount\\\")}}\\n </td>\\n </tr>\\n {%if doc.rate_of_interest > 0%}\\n <tr>\\n <td>\\n {{_(\\\"Interest \\\")}} {{doc.rate_of_interest}}% p.a. ({{doc.overdue_days}} {{_(\\\"days\\\")}})\\n </td>\\n <td style=\\\"text-align: right;\\\">\\n {{doc.get_formatted(\\\"interest_amount\\\")}}\\n </td>\\n </tr>\\n {% endif %}\\n {%if doc.dunning_fee > 0%}\\n <tr>\\n <td>\\n {{_(\\\"Dunning Fee\\\")}}\\n </td>\\n <td style=\\\"text-align: right;\\\">\\n {{doc.get_formatted(\\\"dunning_fee\\\")}}\\n </td>\\n </tr>\\n {% endif %}\\n </tbody>\\n</table>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n<div class=\\\"row total\\\" style =\\\"margin-right: 0px;\\\">\\n\\t\\t<div class=\\\"col-xs-5\\\">\\n\\t\\t\\t<b>{{_(\\\"Grand Total\\\")}}</b></div>\\n\\t\\t<div class=\\\"col-xs-7 text-right\\\" style=\\\"padding-right: 4px;\\\">\\n\\t\\t\\t<b>{{doc.get_formatted(\\\"grand_total\\\")}}</b>\\n\\t\\t</div>\\n</div>\\n\\n\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]",
+ "idx": 0,
+ "line_breaks": 0,
+ "modified": "2020-07-14 18:25:44.348207",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Dunning Letter",
+ "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/gst_pos_invoice/gst_pos_invoice.json b/erpnext/accounts/print_format/gst_pos_invoice/gst_pos_invoice.json
index 1c5a195..1aa1c02 100644
--- a/erpnext/accounts/print_format/gst_pos_invoice/gst_pos_invoice.json
+++ b/erpnext/accounts/print_format/gst_pos_invoice/gst_pos_invoice.json
@@ -7,10 +7,10 @@
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
- "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"40%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"30%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"30%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\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<br>{{ 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<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ 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<br><b>{{ _(\"Serial No\") }}:</b> {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ row.description }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\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<br>{{ 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<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ 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<br><b>{{ _(\"Serial No\") }}:</b> {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ row.description }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
"idx": 0,
"line_breaks": 0,
- "modified": "2019-12-09 17:39:23.356573",
+ "modified": "2020-04-29 16:39:12.936215",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GST POS Invoice",
diff --git a/erpnext/accounts/print_format/pos_invoice/pos_invoice.json b/erpnext/accounts/print_format/pos_invoice/pos_invoice.json
index be69922..13a973d 100644
--- a/erpnext/accounts/print_format/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/print_format/pos_invoice/pos_invoice.json
@@ -6,10 +6,10 @@
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
- "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\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<br>{{ item.item_name }}{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ row.description }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\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<br>{{ item.item_name }}{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ row.description }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
"idx": 1,
"line_breaks": 0,
- "modified": "2019-12-09 17:40:53.183574",
+ "modified": "2020-04-29 16:35:07.043058",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js
index 2aa9618..6abd6e5 100644
--- a/erpnext/accounts/report/accounts_payable/accounts_payable.js
+++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js
@@ -18,41 +18,6 @@
"default": frappe.datetime.get_today()
},
{
- "fieldname":"ageing_based_on",
- "label": __("Ageing Based On"),
- "fieldtype": "Select",
- "options": 'Posting Date\nDue Date\nSupplier Invoice Date',
- "default": "Due Date"
- },
- {
- "fieldname":"range1",
- "label": __("Ageing Range 1"),
- "fieldtype": "Int",
- "default": "30",
- "reqd": 1
- },
- {
- "fieldname":"range2",
- "label": __("Ageing Range 2"),
- "fieldtype": "Int",
- "default": "60",
- "reqd": 1
- },
- {
- "fieldname":"range3",
- "label": __("Ageing Range 3"),
- "fieldtype": "Int",
- "default": "90",
- "reqd": 1
- },
- {
- "fieldname":"range4",
- "label": __("Ageing Range 4"),
- "fieldtype": "Int",
- "default": "120",
- "reqd": 1
- },
- {
"fieldname":"finance_book",
"label": __("Finance Book"),
"fieldtype": "Link",
@@ -89,6 +54,41 @@
}
},
{
+ "fieldname":"ageing_based_on",
+ "label": __("Ageing Based On"),
+ "fieldtype": "Select",
+ "options": 'Posting Date\nDue Date\nSupplier Invoice Date',
+ "default": "Due Date"
+ },
+ {
+ "fieldname":"range1",
+ "label": __("Ageing Range 1"),
+ "fieldtype": "Int",
+ "default": "30",
+ "reqd": 1
+ },
+ {
+ "fieldname":"range2",
+ "label": __("Ageing Range 2"),
+ "fieldtype": "Int",
+ "default": "60",
+ "reqd": 1
+ },
+ {
+ "fieldname":"range3",
+ "label": __("Ageing Range 3"),
+ "fieldtype": "Int",
+ "default": "90",
+ "reqd": 1
+ },
+ {
+ "fieldname":"range4",
+ "label": __("Ageing Range 4"),
+ "fieldtype": "Int",
+ "default": "120",
+ "reqd": 1
+ },
+ {
"fieldname":"payment_terms_template",
"label": __("Payment Terms Template"),
"fieldtype": "Link",
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index 8dc558a..c999eb9 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -18,41 +18,6 @@
"default": frappe.datetime.get_today()
},
{
- "fieldname":"ageing_based_on",
- "label": __("Ageing Based On"),
- "fieldtype": "Select",
- "options": 'Posting Date\nDue Date',
- "default": "Due Date"
- },
- {
- "fieldname":"range1",
- "label": __("Ageing Range 1"),
- "fieldtype": "Int",
- "default": "30",
- "reqd": 1
- },
- {
- "fieldname":"range2",
- "label": __("Ageing Range 2"),
- "fieldtype": "Int",
- "default": "60",
- "reqd": 1
- },
- {
- "fieldname":"range3",
- "label": __("Ageing Range 3"),
- "fieldtype": "Int",
- "default": "90",
- "reqd": 1
- },
- {
- "fieldname":"range4",
- "label": __("Ageing Range 4"),
- "fieldtype": "Int",
- "default": "120",
- "reqd": 1
- },
- {
"fieldname":"finance_book",
"label": __("Finance Book"),
"fieldtype": "Link",
@@ -102,6 +67,41 @@
}
},
{
+ "fieldname":"ageing_based_on",
+ "label": __("Ageing Based On"),
+ "fieldtype": "Select",
+ "options": 'Posting Date\nDue Date',
+ "default": "Due Date"
+ },
+ {
+ "fieldname":"range1",
+ "label": __("Ageing Range 1"),
+ "fieldtype": "Int",
+ "default": "30",
+ "reqd": 1
+ },
+ {
+ "fieldname":"range2",
+ "label": __("Ageing Range 2"),
+ "fieldtype": "Int",
+ "default": "60",
+ "reqd": 1
+ },
+ {
+ "fieldname":"range3",
+ "label": __("Ageing Range 3"),
+ "fieldtype": "Int",
+ "default": "90",
+ "reqd": 1
+ },
+ {
+ "fieldname":"range4",
+ "label": __("Ageing Range 4"),
+ "fieldtype": "Int",
+ "default": "120",
+ "reqd": 1
+ },
+ {
"fieldname":"customer_group",
"label": __("Customer Group"),
"fieldtype": "Link",
@@ -114,12 +114,6 @@
"options": "Payment Terms Template"
},
{
- "fieldname":"territory",
- "label": __("Territory"),
- "fieldtype": "Link",
- "options": "Territory"
- },
- {
"fieldname":"sales_partner",
"label": __("Sales Partner"),
"fieldtype": "Link",
@@ -132,6 +126,12 @@
"options": "Sales Person"
},
{
+ "fieldname":"territory",
+ "label": __("Territory"),
+ "fieldtype": "Link",
+ "options": "Territory"
+ },
+ {
"fieldname": "group_by_party",
"label": __("Group By Customer"),
"fieldtype": "Check"
diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
index 8566f53..fe2bc72 100644
--- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
@@ -334,10 +334,9 @@
def execute(filters=None):
if not filters.periodicity: filters.periodicity = "Monthly"
- period_list = get_period_list(
- filters.from_fiscal_year, filters.to_fiscal_year, filters.periodicity,
- filters.accumulated_values, filters.company
- )
+ period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
+ filters.period_start_date, filters.period_end_date, filters.filter_based_on,
+ filters.periodicity, company=filters.company)
mappers = get_mappers_from_db()
@@ -396,7 +395,7 @@
gl_sum = frappe.db.sql_list("""
select sum(credit) - sum(debit)
from `tabGL Entry`
- where company=%s and posting_date >= %s and posting_date <= %s
+ where company=%s and posting_date >= %s and posting_date <= %s
and voucher_type != 'Period Closing Voucher'
and account in ( SELECT name FROM tabAccount WHERE name IN (%s)
OR parent_account IN (%s))
@@ -405,7 +404,7 @@
gl_sum = frappe.db.sql_list("""
select sum(credit) - sum(debit)
from `tabGL Entry`
- where company=%s and posting_date >= %s and posting_date <= %s
+ where company=%s and posting_date >= %s and posting_date <= %s
and voucher_type != 'Period Closing Voucher'
and account in ( SELECT name FROM tabAccount WHERE name IN (%s)
OR parent_account IN (%s))
diff --git a/erpnext/accounts/report/financial_statements.html b/erpnext/accounts/report/financial_statements.html
index 50947ec..2bb09cf 100644
--- a/erpnext/accounts/report/financial_statements.html
+++ b/erpnext/accounts/report/financial_statements.html
@@ -44,7 +44,7 @@
</tr>
</thead>
<tbody>
- {% for(let j=0, k=data.length-1; j<k; j++) { %}
+ {% for(let j=0, k=data.length; j<k; j++) { %}
{%
var row = data[j];
var row_class = data[j].parent_account ? "" : "financial-statements-important";
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 533685d..3785ebf 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -8,6 +8,7 @@
import re
from past.builtins import cmp
import functools
+import math
import frappe, erpnext
from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency
@@ -45,10 +46,7 @@
start_date = year_start_date
months = get_months(year_start_date, year_end_date)
- if (months // months_to_add) != (months / months_to_add):
- months += months_to_add
-
- for i in range(months // months_to_add):
+ for i in range(math.ceil(months / months_to_add)):
period = frappe._dict({
"from_date": start_date
})
@@ -405,12 +403,12 @@
FROM `tabDistributed Cost Center`
WHERE cost_center IN %(cost_center)s
AND parent NOT IN %(cost_center)s
- AND is_cancelled = 0
GROUP BY parent
) as DCC_allocation
WHERE company=%(company)s
{additional_conditions}
AND posting_date <= %(to_date)s
+ AND is_cancelled = 0
AND cost_center = DCC_allocation.parent
""".format(additional_conditions=additional_conditions.replace("and cost_center in %(cost_center)s ", ''))
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 4e22b05..2563b66 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -223,9 +223,9 @@
# IMP NOTE
# stock_ledger_entries should already be filtered by item_code and warehouse and
# sorted by posting_date desc, posting_time desc
- if item_code in self.non_stock_items:
+ if item_code in self.non_stock_items and (row.project or row.cost_center):
#Issue 6089-Get last purchasing rate for non-stock item
- item_rate = self.get_last_purchase_rate(item_code)
+ item_rate = self.get_last_purchase_rate(item_code, row)
return flt(row.qty) * item_rate
else:
@@ -253,38 +253,34 @@
def get_average_buying_rate(self, row, item_code):
args = row
if not item_code in self.average_buying_rate:
- if item_code in self.non_stock_items:
- self.average_buying_rate[item_code] = flt(frappe.db.sql("""
- select sum(base_net_amount) / sum(qty * conversion_factor)
- from `tabPurchase Invoice Item`
- where item_code = %s and docstatus=1""", item_code)[0][0])
- else:
- args.update({
- 'voucher_type': row.parenttype,
- 'voucher_no': row.parent,
- 'allow_zero_valuation': True,
- 'company': self.filters.company
- })
+ args.update({
+ 'voucher_type': row.parenttype,
+ 'voucher_no': row.parent,
+ 'allow_zero_valuation': True,
+ 'company': self.filters.company
+ })
- average_buying_rate = get_incoming_rate(args)
- self.average_buying_rate[item_code] = flt(average_buying_rate)
+ average_buying_rate = get_incoming_rate(args)
+ self.average_buying_rate[item_code] = flt(average_buying_rate)
return self.average_buying_rate[item_code]
- def get_last_purchase_rate(self, item_code):
+ def get_last_purchase_rate(self, item_code, row):
+ condition = ''
+ if row.project:
+ condition += " AND a.project='%s'" % (row.project)
+ elif row.cost_center:
+ condition += " AND a.cost_center='%s'" % (row.cost_center)
if self.filters.to_date:
- last_purchase_rate = frappe.db.sql("""
- select (a.base_rate / a.conversion_factor)
- from `tabPurchase Invoice Item` a
- where a.item_code = %s and a.docstatus=1
- and modified <= %s
- order by a.modified desc limit 1""", (item_code, self.filters.to_date))
- else:
- last_purchase_rate = frappe.db.sql("""
- select (a.base_rate / a.conversion_factor)
- from `tabPurchase Invoice Item` a
- where a.item_code = %s and a.docstatus=1
- order by a.modified desc limit 1""", item_code)
+ condition += " AND modified='%s'" % (self.filters.to_date)
+
+ last_purchase_rate = frappe.db.sql("""
+ select (a.base_rate / a.conversion_factor)
+ from `tabPurchase Invoice Item` a
+ where a.item_code = %s and a.docstatus=1
+ {0}
+ order by a.modified desc limit 1""".format(condition), item_code)
+
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
def load_invoice_items(self):
@@ -321,7 +317,8 @@
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.dn_detail,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
- `tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return
+ `tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
+ `tabSales Invoice Item`.cost_center
{sales_person_cols}
from
`tabSales Invoice` inner join `tabSales Invoice Item`
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 5a699b6..3cf0870 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -152,6 +152,7 @@
{additional_conditions}
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
and account in (select name from `tabAccount` where report_type=%(report_type)s)
+ and is_cancelled = 0
group by account""".format(additional_conditions=additional_conditions), query_filters , as_dict=True)
opening = frappe._dict()
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 16146f4..51ac7cf 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -122,7 +122,7 @@
cost_center = frappe.form_dict.get("cost_center")
- cond = []
+ cond = ["is_cancelled=0"]
if date:
cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
else:
@@ -133,7 +133,7 @@
acc = frappe.get_doc("Account", account)
try:
- year_start_date = get_fiscal_year(date, verbose=0)[1]
+ year_start_date = get_fiscal_year(date, company=company, verbose=0)[1]
except FiscalYearError:
if getdate(date) > getdate(nowdate()):
# if fiscal year not found and the date is greater than today
@@ -144,14 +144,12 @@
# hence, assuming balance as 0.0
return 0.0
- allow_cost_center_in_entry_of_bs_account = get_allow_cost_center_in_entry_of_bs_account()
-
if account:
report_type = acc.report_type
else:
report_type = ""
- if cost_center and (allow_cost_center_in_entry_of_bs_account or report_type =='Profit and Loss'):
+ if cost_center and report_type == 'Profit and Loss':
cc = frappe.get_doc("Cost Center", cost_center)
if cc.is_group:
cond.append(""" exists (
@@ -208,7 +206,7 @@
return flt(bal)
def get_count_on(account, fieldname, date):
- cond = []
+ cond = ["is_cancelled=0"]
if date:
cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
else:
@@ -678,7 +676,8 @@
invoice_list = frappe.db.sql("""
select
voucher_no, voucher_type, posting_date, due_date,
- ifnull(sum({dr_or_cr}), 0) as invoice_amount
+ ifnull(sum({dr_or_cr}), 0) as invoice_amount,
+ account_currency as currency
from
`tabGL Entry`
where
@@ -735,7 +734,8 @@
'invoice_amount': flt(d.invoice_amount),
'payment_amount': payment_amount,
'outstanding_amount': outstanding_amount,
- 'due_date': d.due_date
+ 'due_date': d.due_date,
+ 'currency': d.currency
})
)
@@ -787,10 +787,10 @@
company_currency = frappe.get_cached_value('Company', company, "default_currency")
for each in acc:
each["company_currency"] = company_currency
- each["balance"] = flt(get_balance_on(each.get("value"), in_account_currency=False))
+ each["balance"] = flt(get_balance_on(each.get("value"), in_account_currency=False, company=company))
if each.account_currency != company_currency:
- each["balance_in_account_currency"] = flt(get_balance_on(each.get("value")))
+ each["balance_in_account_currency"] = flt(get_balance_on(each.get("value"), company=company))
return acc
@@ -897,11 +897,6 @@
return accounts
-def get_allow_cost_center_in_entry_of_bs_account():
- def generator():
- return cint(frappe.db.get_value('Accounts Settings', None, 'allow_cost_center_in_entry_of_bs_account'))
- return frappe.local_cache("get_allow_cost_center_in_entry_of_bs_account", (), generator, regenerate_if_none=True)
-
def get_stock_accounts(company):
return frappe.get_all("Account", filters = {
"account_type": "Stock",
diff --git a/erpnext/assets/assets_dashboard/asset/asset.json b/erpnext/assets/assets_dashboard/asset/asset.json
new file mode 100644
index 0000000..56b1e2a
--- /dev/null
+++ b/erpnext/assets/assets_dashboard/asset/asset.json
@@ -0,0 +1,39 @@
+{
+ "cards": [
+ {
+ "card": "Total Assets"
+ },
+ {
+ "card": "New Assets (This Year)"
+ },
+ {
+ "card": "Asset Value"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "Asset Value Analytics",
+ "width": "Full"
+ },
+ {
+ "chart": "Category-wise Asset Value",
+ "width": "Half"
+ },
+ {
+ "chart": "Location-wise Asset Value",
+ "width": "Half"
+ }
+ ],
+ "creation": "2020-07-14 18:23:53.343082",
+ "dashboard_name": "Asset",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 0,
+ "is_standard": 1,
+ "modified": "2020-07-21 18:14:25.078929",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/assets/dashboard_chart/asset_value_analytics/asset_value_analytics.json b/erpnext/assets/dashboard_chart/asset_value_analytics/asset_value_analytics.json
new file mode 100644
index 0000000..bc2edc9
--- /dev/null
+++ b/erpnext/assets/dashboard_chart/asset_value_analytics/asset_value_analytics.json
@@ -0,0 +1,27 @@
+{
+ "chart_name": "Asset Value Analytics",
+ "chart_type": "Report",
+ "creation": "2020-07-14 18:23:53.091233",
+ "custom_options": "{\"type\": \"bar\", \"barOptions\": {\"stacked\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
+ "filters_json": "{\"status\":\"In Location\",\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"date_based_on\":\"Purchase Date\",\"group_by\":\"--Select a group--\"}",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "modified": "2020-07-23 13:53:33.211371",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Value Analytics",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Fixed Asset Register",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/assets/dashboard_chart/category_wise_asset_value/category_wise_asset_value.json b/erpnext/assets/dashboard_chart/category_wise_asset_value/category_wise_asset_value.json
new file mode 100644
index 0000000..e79d2d7
--- /dev/null
+++ b/erpnext/assets/dashboard_chart/category_wise_asset_value/category_wise_asset_value.json
@@ -0,0 +1,29 @@
+{
+ "chart_name": "Category-wise Asset Value",
+ "chart_type": "Report",
+ "creation": "2020-07-14 18:23:53.146304",
+ "custom_options": "{\"type\": \"donut\", \"height\": 300, \"axisOptions\": {\"shortenYAxisNumbers\": 1}}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
+ "filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "modified": "2020-07-23 13:39:32.429240",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Category-wise Asset Value",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Fixed Asset Register",
+ "timeseries": 0,
+ "type": "Donut",
+ "use_report_chart": 0,
+ "x_field": "asset_category",
+ "y_axis": [
+ {
+ "y_field": "asset_value"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/assets/dashboard_chart/location_wise_asset_value/location_wise_asset_value.json b/erpnext/assets/dashboard_chart/location_wise_asset_value/location_wise_asset_value.json
new file mode 100644
index 0000000..481586e
--- /dev/null
+++ b/erpnext/assets/dashboard_chart/location_wise_asset_value/location_wise_asset_value.json
@@ -0,0 +1,29 @@
+{
+ "chart_name": "Location-wise Asset Value",
+ "chart_type": "Report",
+ "creation": "2020-07-14 18:23:53.195389",
+ "custom_options": "{\"type\": \"donut\", \"height\": 300, \"axisOptions\": {\"shortenYAxisNumbers\": 1}}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
+ "filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "modified": "2020-07-23 13:42:44.912551",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Location-wise Asset Value",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Fixed Asset Register",
+ "timeseries": 0,
+ "type": "Donut",
+ "use_report_chart": 0,
+ "x_field": "location",
+ "y_axis": [
+ {
+ "y_field": "asset_value"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 2ecabe6..0bd03a8 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -35,11 +35,9 @@
if not self.booked_fixed_asset and self.validate_make_gl_entry():
self.make_gl_entries()
- def before_cancel(self):
- self.cancel_auto_gen_movement()
-
def on_cancel(self):
self.validate_cancellation()
+ self.cancel_movement_entries()
self.delete_depreciation_entries()
self.set_status()
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
@@ -134,19 +132,6 @@
Please do not book expense of multiple assets against one single Asset.")
.format(frappe.bold("equal"), "<br>"), title=_("Invalid Gross Purchase Amount"))
- def cancel_auto_gen_movement(self):
- movements = frappe.db.sql(
- """SELECT asm.name, asm.docstatus
- FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
- WHERE asm_item.parent=asm.name and asm_item.asset=%s and asm.docstatus=1""", self.name, as_dict=1)
- if len(movements) > 1:
- frappe.throw(_('Asset has multiple Asset Movement Entries which has to be \
- cancelled manually to cancel this asset.'))
- if movements:
- movement = frappe.get_doc('Asset Movement', movements[0].get('name'))
- movement.flags.ignore_validate = True
- movement.cancel()
-
def make_asset_movement(self):
reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
reference_docname = self.purchase_receipt or self.purchase_invoice
@@ -408,9 +393,21 @@
row.expected_value_after_useful_life = asset_value_after_full_schedule
def validate_cancellation(self):
+ if self.status in ("In Maintenance", "Out of Order"):
+ frappe.throw(_("There are active maintenance or repairs against the asset. You must complete all of them before cancelling the asset."))
if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"):
frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status))
+ def cancel_movement_entries(self):
+ movements = frappe.db.sql(
+ """SELECT asm.name, asm.docstatus
+ FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
+ WHERE asm_item.parent=asm.name and asm_item.asset=%s and asm.docstatus=1""", self.name, as_dict=1)
+
+ for movement in movements:
+ movement = frappe.get_doc('Asset Movement', movement.get('name'))
+ movement.cancel()
+
def delete_depreciation_entries(self):
for d in self.get("schedules"):
if d.journal_entry:
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 522c1fe..8f0afb4 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -10,7 +10,7 @@
def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled
- if not cint(frappe.db.get_single_value("Accounts Settings", "book_asset_depreciation_entry_automatically")):
+ if not cint(frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")):
return
if not date:
@@ -58,7 +58,8 @@
"account": accumulated_depreciation_account,
"credit_in_account_currency": d.depreciation_amount,
"reference_type": "Asset",
- "reference_name": asset.name
+ "reference_name": asset.name,
+ "cost_center": ""
}
debit_entry = {
@@ -196,12 +197,14 @@
{
"account": fixed_asset_account,
"credit_in_account_currency": asset.gross_purchase_amount,
- "credit": asset.gross_purchase_amount
+ "credit": asset.gross_purchase_amount,
+ "cost_center": depreciation_cost_center
},
{
"account": accumulated_depr_account,
"debit_in_account_currency": accumulated_depr_amount,
- "debit": accumulated_depr_amount
+ "debit": accumulated_depr_amount,
+ "cost_center": depreciation_cost_center
}
]
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
index 1869a29..60c528b 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
@@ -106,6 +106,7 @@
maintenance_log.save()
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_team_members(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.get_values('Maintenance Team Member', { 'parent': filters.get("maintenance_team") })
diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py
index f169f01..148357f 100644
--- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py
+++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py
@@ -41,6 +41,7 @@
asset_maintenance_doc.save()
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_maintenance_tasks(doctype, txt, searchfield, start, page_len, filters):
asset_maintenance_tasks = frappe.db.get_values('Asset Maintenance Task', {'parent':filters.get("asset_maintenance")}, 'maintenance_task')
return asset_maintenance_tasks
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py
index 3da355e..b2de250 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.py
@@ -87,33 +87,9 @@
def on_submit(self):
self.set_latest_location_in_asset()
-
- def before_cancel(self):
- self.validate_last_movement()
def on_cancel(self):
self.set_latest_location_in_asset()
-
- def validate_last_movement(self):
- for d in self.assets:
- auto_gen_movement_entry = frappe.db.sql(
- """
- SELECT asm.name
- FROM `tabAsset Movement Item` asm_item, `tabAsset Movement` asm
- WHERE
- asm.docstatus=1 and
- asm_item.parent=asm.name and
- asm_item.asset=%s and
- asm.company=%s and
- asm_item.source_location is NULL and
- asm.purpose=%s
- ORDER BY
- asm.transaction_date asc
- """, (d.asset, self.company, 'Receipt'), as_dict=1)
-
- if auto_gen_movement_entry and auto_gen_movement_entry[0].get('name') == self.name:
- frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \
- auto generated for Asset {1}').format(self.name, d.asset))
def set_latest_location_in_asset(self):
current_location, current_employee = '', ''
diff --git a/erpnext/assets/module_onboarding/assets/assets.json b/erpnext/assets/module_onboarding/assets/assets.json
index 66dd60a..1086ab4 100644
--- a/erpnext/assets/module_onboarding/assets/assets.json
+++ b/erpnext/assets/module_onboarding/assets/assets.json
@@ -13,7 +13,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/asset",
"idx": 0,
"is_complete": 0,
- "modified": "2020-05-08 16:17:31.685943",
+ "modified": "2020-07-08 14:05:51.828497",
"modified_by": "Administrator",
"module": "Assets",
"name": "Assets",
@@ -27,7 +27,7 @@
},
{
"step": "Create an Asset Category"
- },
+ },
{
"step": "Purchase an Asset Item"
},
@@ -35,8 +35,7 @@
"step": "Create an Asset"
}
],
- "subtitle": "Assets, Depreciations, Repairs and more",
- "success_message": "The Asset Module is all set up!",
- "title": "Let's Setup Asset Management",
- "user_can_dismiss": 1
+ "subtitle": "Assets, Depreciations, Repairs, and more.",
+ "success_message": "The Assets Module is all set up!",
+ "title": "Let's Set Up the Assets Module."
}
\ No newline at end of file
diff --git a/erpnext/assets/number_card/asset_value/asset_value.json b/erpnext/assets/number_card/asset_value/asset_value.json
new file mode 100644
index 0000000..68e5f54
--- /dev/null
+++ b/erpnext/assets/number_card/asset_value/asset_value.json
@@ -0,0 +1,21 @@
+{
+ "aggregate_function_based_on": "value_after_depreciation",
+ "creation": "2020-07-14 18:23:53.302457",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Asset",
+ "filters_json": "[]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Asset Value",
+ "modified": "2020-07-21 18:13:47.647997",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Value",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git "a/erpnext/assets/number_card/new_assets_\050this_year\051/new_assets_\050this_year\051.json" "b/erpnext/assets/number_card/new_assets_\050this_year\051/new_assets_\050this_year\051.json"
new file mode 100644
index 0000000..6c8fb35
--- /dev/null
+++ "b/erpnext/assets/number_card/new_assets_\050this_year\051/new_assets_\050this_year\051.json"
@@ -0,0 +1,20 @@
+{
+ "creation": "2020-07-14 18:23:53.267919",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Asset",
+ "filters_json": "[[\"Asset\",\"creation\",\"Timespan\",\"this year\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "New Assets (This Year)",
+ "modified": "2020-07-23 13:45:20.418766",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "New Assets (This Year)",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/assets/number_card/total_assets/total_assets.json b/erpnext/assets/number_card/total_assets/total_assets.json
new file mode 100644
index 0000000..d127de8
--- /dev/null
+++ b/erpnext/assets/number_card/total_assets/total_assets.json
@@ -0,0 +1,20 @@
+{
+ "creation": "2020-07-14 18:23:53.233328",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Asset",
+ "filters_json": "[]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Assets",
+ "modified": "2020-07-21 18:12:51.664292",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Total Assets",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/create_a_fixed_asset_item/create_a_fixed_asset_item.json b/erpnext/assets/onboarding_step/create_a_fixed_asset_item/create_a_fixed_asset_item.json
index f5818c0..51702d9 100644
--- a/erpnext/assets/onboarding_step/create_a_fixed_asset_item/create_a_fixed_asset_item.json
+++ b/erpnext/assets/onboarding_step/create_a_fixed_asset_item/create_a_fixed_asset_item.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
+ "is_single": 0,
"is_skipped": 0,
"modified": "2020-05-08 13:20:00.259985",
"modified_by": "Administrator",
"name": "Create a Fixed Asset Item",
"owner": "Administrator",
"reference_document": "Item",
- "title": "Create a Fixed Asset Item"
+ "show_full_form": 0,
+ "title": "Create a Fixed Asset Item",
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/create_an_asset/create_an_asset.json b/erpnext/assets/onboarding_step/create_an_asset/create_an_asset.json
index 5488b1d..b4f8a05 100644
--- a/erpnext/assets/onboarding_step/create_an_asset/create_an_asset.json
+++ b/erpnext/assets/onboarding_step/create_an_asset/create_an_asset.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
+ "is_single": 0,
"is_skipped": 0,
"modified": "2020-05-08 13:21:53.332538",
"modified_by": "Administrator",
"name": "Create an Asset",
"owner": "Administrator",
"reference_document": "Asset",
- "title": "Create an Asset"
+ "show_full_form": 0,
+ "title": "Create an Asset",
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/create_an_asset_category/create_an_asset_category.json b/erpnext/assets/onboarding_step/create_an_asset_category/create_an_asset_category.json
index 3bf54af..ffdb954 100644
--- a/erpnext/assets/onboarding_step/create_an_asset_category/create_an_asset_category.json
+++ b/erpnext/assets/onboarding_step/create_an_asset_category/create_an_asset_category.json
@@ -1,16 +1,19 @@
{
- "action": "Create Entry",
- "creation": "2020-05-08 13:21:53.332538",
- "docstatus": 0,
- "doctype": "Onboarding Step",
- "idx": 0,
- "is_complete": 0,
- "is_mandatory": 0,
- "is_skipped": 0,
- "modified": "2020-05-08 13:21:53.332538",
- "modified_by": "Administrator",
- "name": "Create an Asset Category",
- "owner": "Administrator",
- "reference_document": "Asset Category",
- "title": "Create an Asset Category"
- }
\ No newline at end of file
+ "action": "Create Entry",
+ "creation": "2020-05-08 13:21:53.332538",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-08 13:21:53.332538",
+ "modified_by": "Administrator",
+ "name": "Create an Asset Category",
+ "owner": "Administrator",
+ "reference_document": "Asset Category",
+ "show_full_form": 0,
+ "title": "Create an Asset Category",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/introduction_to_assets/introduction_to_assets.json b/erpnext/assets/onboarding_step/introduction_to_assets/introduction_to_assets.json
index d48dd1c..d89da27 100644
--- a/erpnext/assets/onboarding_step/introduction_to_assets/introduction_to_assets.json
+++ b/erpnext/assets/onboarding_step/introduction_to_assets/introduction_to_assets.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
+ "is_single": 0,
"is_skipped": 0,
"modified": "2020-05-08 16:06:16.625646",
"modified_by": "Administrator",
"name": "Introduction to Assets",
"owner": "Administrator",
+ "show_full_form": 0,
"title": "Introduction to Assets",
+ "validate_action": 0,
"video_url": "https://www.youtube.com/watch?v=I-K8pLRmvSo"
}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/purchase_an_asset_item/purchase_an_asset_item.json b/erpnext/assets/onboarding_step/purchase_an_asset_item/purchase_an_asset_item.json
index 732ff7f..ce3185e 100644
--- a/erpnext/assets/onboarding_step/purchase_an_asset_item/purchase_an_asset_item.json
+++ b/erpnext/assets/onboarding_step/purchase_an_asset_item/purchase_an_asset_item.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
+ "is_single": 0,
"is_skipped": 0,
"modified": "2020-05-08 13:21:28.208059",
"modified_by": "Administrator",
"name": "Purchase an Asset Item",
"owner": "Administrator",
"reference_document": "Purchase Receipt",
- "title": "Purchase an Asset Item"
+ "show_full_form": 0,
+ "title": "Purchase an Asset Item",
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/buying/buying_dashboard/buying/buying.json b/erpnext/buying/buying_dashboard/buying/buying.json
new file mode 100644
index 0000000..ab7ebac
--- /dev/null
+++ b/erpnext/buying/buying_dashboard/buying/buying.json
@@ -0,0 +1,46 @@
+{
+ "cards": [
+ {
+ "card": "Annual Purchase"
+ },
+ {
+ "card": "Purchase Orders to Receive"
+ },
+ {
+ "card": "Purchase Orders to Bill"
+ },
+ {
+ "card": "Active Suppliers"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "Purchase Order Trends",
+ "width": "Full"
+ },
+ {
+ "chart": "Material Request Analysis",
+ "width": "Half"
+ },
+ {
+ "chart": "Purchase Order Analysis",
+ "width": "Half"
+ },
+ {
+ "chart": "Top Suppliers",
+ "width": "Full"
+ }
+ ],
+ "creation": "2020-07-20 21:01:02.541065",
+ "dashboard_name": "Buying",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 12:48:38.112744",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Buying",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/buying/dashboard_chart/material_request_analysis/material_request_analysis.json b/erpnext/buying/dashboard_chart/material_request_analysis/material_request_analysis.json
new file mode 100644
index 0000000..0b66f1f
--- /dev/null
+++ b/erpnext/buying/dashboard_chart/material_request_analysis/material_request_analysis.json
@@ -0,0 +1,27 @@
+{
+ "chart_name": "Material Request Analysis",
+ "chart_type": "Group By",
+ "creation": "2020-07-20 21:01:02.242563",
+ "custom_options": "{\"height\": 300}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Material Request",
+ "dynamic_filters_json": "[[\"Material Request\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Material Request\",\"status\",\"not in\",[\"Draft\",\"Cancelled\",\"Stopped\",null],false],[\"Material Request\",\"material_request_type\",\"=\",\"Purchase\",false],[\"Material Request\",\"docstatus\",\"=\",\"1\",false],[\"Material Request\",\"transaction_date\",\"Timespan\",\"last quarter\",false]]",
+ "group_by_based_on": "status",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 12:43:56.961250",
+ "modified": "2020-07-22 21:20:51.840194",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Material Request Analysis",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Donut",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/buying/dashboard_chart/purchase_order_analysis/purchase_order_analysis.json b/erpnext/buying/dashboard_chart/purchase_order_analysis/purchase_order_analysis.json
new file mode 100644
index 0000000..020755b
--- /dev/null
+++ b/erpnext/buying/dashboard_chart/purchase_order_analysis/purchase_order_analysis.json
@@ -0,0 +1,24 @@
+{
+ "chart_name": "Purchase Order Analysis",
+ "chart_type": "Report",
+ "creation": "2020-07-20 21:01:02.203880",
+ "custom_options": "{\"type\": \"donut\", \"height\": 300, \"axisOptions\": {\"shortenYAxisNumbers\": 1}}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -1)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
+ "filters_json": "{\"group_by_po\":0}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 12:44:35.754973",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Purchase Order Analysis",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Purchase Order Analysis",
+ "timeseries": 0,
+ "type": "Donut",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json b/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json
new file mode 100644
index 0000000..6452ed2
--- /dev/null
+++ b/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json
@@ -0,0 +1,24 @@
+{
+ "chart_name": "Purchase Order Trends",
+ "chart_type": "Report",
+ "creation": "2020-07-20 21:01:02.295012",
+ "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
+ "filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Item\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-21 16:13:25.092287",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Purchase Order Trends",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Purchase Order Trends",
+ "timeseries": 0,
+ "type": "Line",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json b/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json
new file mode 100644
index 0000000..6f7da8e
--- /dev/null
+++ b/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json
@@ -0,0 +1,23 @@
+{
+ "chart_name": "Top Suppliers",
+ "chart_type": "Report",
+ "creation": "2020-07-20 21:01:02.329519",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
+ "filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Supplier\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 12:43:40.829652",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Top Suppliers",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Purchase Receipt Trends",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/buying/dashboard_fixtures.py b/erpnext/buying/dashboard_fixtures.py
deleted file mode 100644
index c6e2ffa..0000000
--- a/erpnext/buying/dashboard_fixtures.py
+++ /dev/null
@@ -1,211 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-import json
-from frappe import _
-from frappe.utils import nowdate
-from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
-
-def get_data():
-
- fiscal_year = _get_fiscal_year(nowdate())
-
- if not fiscal_year:
- return frappe._dict()
-
- company = frappe.get_doc("Company", get_company_for_dashboards())
- fiscal_year_name = fiscal_year.get("name")
- start_date = str(fiscal_year.get("year_start_date"))
- end_date = str(fiscal_year.get("year_end_date"))
-
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(company, fiscal_year_name, start_date, end_date),
- "number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date),
- })
-
-def get_company_for_dashboards():
- company = frappe.defaults.get_defaults().company
- if company:
- return company
- else:
- company_list = frappe.get_list("Company")
- if company_list:
- return company_list[0].name
- return None
-
-def get_dashboards():
- return [{
- "name": "Buying",
- "dashboard_name": "Buying",
- "charts": [
- { "chart": "Purchase Order Trends", "width": "Full"},
- { "chart": "Material Request Analysis", "width": "Half"},
- { "chart": "Purchase Order Analysis", "width": "Half"},
- { "chart": "Top Suppliers", "width": "Full"}
- ],
- "cards": [
- { "card": "Annual Purchase"},
- { "card": "Purchase Orders to Receive"},
- { "card": "Purchase Orders to Bill"},
- { "card": "Active Suppliers"}
- ]
- }]
-
-def get_charts(company, fiscal_year_name, start_date, end_date):
- return [
- {
- "name": "Purchase Order Analysis",
- "chart_name": _("Purchase Order Analysis"),
- "chart_type": "Report",
- "custom_options": json.dumps({
- "type": "donut",
- "height": 300,
- "axisOptions": {"shortenYAxisNumbers": 1}
- }),
- "doctype": "Dashboard Chart",
- "filters_json": json.dumps({
- "company": company.name,
- "from_date": start_date,
- "to_date": end_date
- }),
- "is_custom": 1,
- "is_public": 1,
- "owner": "Administrator",
- "report_name": "Purchase Order Analysis",
- "type": "Donut"
- },
- {
- "name": "Material Request Analysis",
- "chart_name": _("Material Request Analysis"),
- "chart_type": "Group By",
- "custom_options": json.dumps({"height": 300}),
- "doctype": "Dashboard Chart",
- "document_type": "Material Request",
- "filters_json": json.dumps(
- [["Material Request", "status", "not in", ["Draft", "Cancelled", "Stopped", None], False],
- ["Material Request", "material_request_type", "=", "Purchase", False],
- ["Material Request", "company", "=", company.name, False],
- ["Material Request", "docstatus", "=", 1, False],
- ["Material Request", "transaction_date", "Between", [start_date, end_date], False]]
- ),
- "group_by_based_on": "status",
- "group_by_type": "Count",
- "is_custom": 0,
- "is_public": 1,
- "number_of_groups": 0,
- "owner": "Administrator",
- "type": "Donut"
- },
- {
- "name": "Purchase Order Trends",
- "chart_name": _("Purchase Order Trends"),
- "chart_type": "Report",
- "custom_options": json.dumps({
- "type": "line",
- "axisOptions": {"shortenYAxisNumbers": 1},
- "tooltipOptions": {},
- "lineOptions": {
- "regionFill": 1
- }
- }),
- "doctype": "Dashboard Chart",
- "filters_json": json.dumps({
- "company": company.name,
- "period": "Monthly",
- "fiscal_year": fiscal_year_name,
- "period_based_on": "posting_date",
- "based_on": "Item"
- }),
- "is_custom": 1,
- "is_public": 1,
- "owner": "Administrator",
- "report_name": "Purchase Order Trends",
- "type": "Line"
- },
- {
- "name": "Top Suppliers",
- "chart_name": _("Top Suppliers"),
- "chart_type": "Report",
- "doctype": "Dashboard Chart",
- "filters_json": json.dumps({
- "company": company.name,
- "period": "Monthly",
- "fiscal_year": fiscal_year_name,
- "period_based_on": "posting_date",
- "based_on": "Supplier"
- }),
- "is_custom": 1,
- "is_public": 1,
- "owner": "Administrator",
- "report_name": "Purchase Receipt Trends",
- "type": "Bar"
- }
- ]
-
-def get_number_cards(company, fiscal_year_name, start_date, end_date):
- return [
- {
- "name": "Annual Purchase",
- "aggregate_function_based_on": "base_net_total",
- "doctype": "Number Card",
- "document_type": "Purchase Order",
- "filters_json": json.dumps([
- ["Purchase Order", "transaction_date", "Between", [start_date, end_date], False],
- ["Purchase Order", "status", "not in", ["Draft", "Cancelled", "Closed", None], False],
- ["Purchase Order", "docstatus", "=", 1, False],
- ["Purchase Order", "company", "=", company.name, False]
- ]),
- "function": "Sum",
- "is_public": 1,
- "label": _("Annual Purchase"),
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Monthly"
- },
- {
- "name": "Purchase Orders to Receive",
- "doctype": "Number Card",
- "document_type": "Purchase Order",
- "filters_json": json.dumps([
- ["Purchase Order", "status", "in", ["To Receive and Bill", "To Receive", None], False],
- ["Purchase Order", "docstatus", "=", 1, False],
- ["Purchase Order", "company", "=", company.name, False]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Purchase Orders to Receive"),
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- },
- {
- "name": "Purchase Orders to Bill",
- "doctype": "Number Card",
- "document_type": "Purchase Order",
- "filters_json": json.dumps([
- ["Purchase Order", "status", "in", ["To Receive and Bill", "To Bill", None], False],
- ["Purchase Order", "docstatus", "=", 1, False],
- ["Purchase Order", "company", "=", company.name, False]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Purchase Orders to Bill"),
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- },
- {
- "name": "Active Suppliers",
- "doctype": "Number Card",
- "document_type": "Supplier",
- "filters_json": json.dumps([["Supplier", "disabled", "=", "0"]]),
- "function": "Count",
- "is_public": 1,
- "label": "Active Suppliers",
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Monthly"
- }
- ]
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 4a8146a..25065ab 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -7,12 +7,6 @@
frappe.ui.form.on("Purchase Order", {
setup: function(frm) {
- frm.custom_make_buttons = {
- 'Purchase Receipt': 'Receipt',
- 'Purchase Invoice': 'Invoice',
- 'Stock Entry': 'Material to Supplier',
- 'Payment Entry': 'Payment'
- }
frm.set_query("reserve_warehouse", "supplied_items", function() {
return {
@@ -36,20 +30,6 @@
},
- refresh: function(frm) {
- if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed'
- && flt(frm.doc.per_received) < 100 && flt(frm.doc.per_billed) < 100) {
- frm.add_custom_button(__('Update Items'), () => {
- erpnext.utils.update_child_items({
- frm: frm,
- child_docname: "items",
- child_doctype: "Purchase Order Detail",
- cannot_add_row: false,
- })
- });
- }
- },
-
onload: function(frm) {
set_schedule_date(frm);
if (!frm.doc.transaction_date){
@@ -76,6 +56,18 @@
});
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
+ setup: function() {
+ this.frm.custom_make_buttons = {
+ 'Purchase Receipt': 'Receipt',
+ 'Purchase Invoice': 'Invoice',
+ 'Stock Entry': 'Material to Supplier',
+ 'Payment Entry': 'Payment',
+ }
+
+ this._super();
+
+ },
+
refresh: function(doc, cdt, cdn) {
var me = this;
this._super();
@@ -99,6 +91,16 @@
if(doc.docstatus == 1) {
if(!in_list(["Closed", "Delivered"], doc.status)) {
+ if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) {
+ this.frm.add_custom_button(__('Update Items'), () => {
+ erpnext.utils.update_child_items({
+ frm: frm,
+ child_docname: "items",
+ child_doctype: "Purchase Order Detail",
+ cannot_add_row: false,
+ })
+ });
+ }
if (this.frm.has_perm("submit")) {
if(flt(doc.per_billed, 6) < 100 || flt(doc.per_received, 6) < 100) {
if (doc.status != "On Hold") {
@@ -123,14 +125,14 @@
}
if(doc.status != "Closed") {
if (doc.status != "On Hold") {
- if(flt(doc.per_received, 2) < 100 && allow_receipt) {
+ if(flt(doc.per_received) < 100 && allow_receipt) {
cur_frm.add_custom_button(__('Receipt'), this.make_purchase_receipt, __('Create'));
if(doc.is_subcontracted==="Yes" && me.has_unsupplied_items()) {
cur_frm.add_custom_button(__('Material to Supplier'),
function() { me.make_stock_entry(); }, __("Transfer"));
}
}
- if(flt(doc.per_billed, 2) < 100)
+ if(flt(doc.per_billed) < 100)
cur_frm.add_custom_button(__('Invoice'),
this.make_purchase_invoice, __('Create'));
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 13f5cb0..502dbba 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -137,9 +137,7 @@
{
"fieldname": "supplier_section",
"fieldtype": "Section Break",
- "options": "fa fa-user",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-user"
},
{
"allow_on_submit": 1,
@@ -149,9 +147,7 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "naming_series",
@@ -163,9 +159,7 @@
"options": "PUR-ORD-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"bold": 1,
@@ -178,18 +172,14 @@
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
- "search_index": 1,
- "show_days": 1,
- "show_seconds": 1
+ "search_index": 1
},
{
"depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))",
"description": "Fetch items based on Default Supplier.",
"fieldname": "get_items_from_open_material_requests",
"fieldtype": "Button",
- "label": "Get Items from Open Material Requests",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Get Items from Open Material Requests"
},
{
"bold": 1,
@@ -198,9 +188,7 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Supplier Name",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "company",
@@ -212,17 +200,13 @@
"options": "Company",
"print_hide": 1,
"remember_last_selected_value": 1,
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -234,35 +218,27 @@
"oldfieldname": "transaction_date",
"oldfieldtype": "Date",
"reqd": 1,
- "search_index": 1,
- "show_days": 1,
- "show_seconds": 1
+ "search_index": 1
},
{
"allow_on_submit": 1,
"fieldname": "schedule_date",
"fieldtype": "Date",
- "label": "Required By",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Required By"
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.docstatus===1",
"fieldname": "order_confirmation_no",
"fieldtype": "Data",
- "label": "Order Confirmation No",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Order Confirmation No"
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.order_confirmation_no",
"fieldname": "order_confirmation_date",
"fieldtype": "Date",
- "label": "Order Confirmation Date",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Order Confirmation Date"
},
{
"fieldname": "amended_from",
@@ -274,25 +250,19 @@
"oldfieldtype": "Data",
"options": "Purchase Order",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "drop_ship",
"fieldtype": "Section Break",
- "label": "Drop Ship",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Drop Ship"
},
{
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"options": "Customer",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"bold": 1,
@@ -300,41 +270,31 @@
"fieldtype": "Data",
"label": "Customer Name",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_19",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "customer_contact_person",
"fieldtype": "Link",
"label": "Customer Contact",
- "options": "Contact",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Contact"
},
{
"fieldname": "customer_contact_display",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Customer Contact",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "customer_contact_mobile",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Customer Mobile No",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "customer_contact_email",
@@ -342,60 +302,46 @@
"hidden": 1,
"label": "Customer Contact Email",
"options": "Email",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Address and Contact"
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"in_global_search": 1,
"label": "Contact",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "contact_email",
@@ -403,42 +349,32 @@
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-tag"
},
{
"fieldname": "currency",
@@ -448,9 +384,7 @@
"oldfieldtype": "Select",
"options": "Currency",
"print_hide": 1,
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "conversion_rate",
@@ -460,24 +394,18 @@
"oldfieldtype": "Currency",
"precision": "9",
"print_hide": 1,
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "cb_price_list",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "buying_price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "price_list_currency",
@@ -485,18 +413,14 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"default": "0",
@@ -505,15 +429,11 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"description": "Sets 'Warehouse' in each row of the Items table.",
@@ -521,15 +441,11 @@
"fieldtype": "Link",
"label": "Set Target Warehouse",
"options": "Warehouse",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"default": "No",
@@ -538,33 +454,25 @@
"in_standard_filter": 1,
"label": "Supply Raw Materials",
"options": "No\nYes",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
- "options": "Warehouse",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Warehouse"
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-shopping-cart"
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Scan Barcode"
},
{
"allow_bulk_edit": 1,
@@ -574,34 +482,26 @@
"oldfieldname": "po_details",
"oldfieldtype": "Table",
"options": "Purchase Order Item",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"collapsible": 1,
"fieldname": "section_break_48",
"fieldtype": "Section Break",
- "label": "Pricing Rules",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Pricing Rules"
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Purchase Order Pricing Rule",
"options": "Pricing Rule Detail",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible_depends_on": "supplied_items",
"fieldname": "raw_material_details",
"fieldtype": "Section Break",
- "label": "Raw Materials Supplied",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Raw Materials Supplied"
},
{
"fieldname": "supplied_items",
@@ -611,23 +511,17 @@
"oldfieldtype": "Table",
"options": "Purchase Order Item Supplied",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "sb_last_purchase",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_total",
@@ -635,9 +529,7 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_net_total",
@@ -648,24 +540,18 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_26",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "net_total",
@@ -675,26 +561,20 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-money"
},
{
"fieldname": "taxes_and_charges",
@@ -703,30 +583,22 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break_50",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
"options": "Shipping Rule",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "section_break_52",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "taxes",
@@ -734,17 +606,13 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Purchase Taxes and Charges"
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Tax Breakup"
},
{
"fieldname": "other_charges_calculation",
@@ -753,17 +621,13 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-money"
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -773,9 +637,7 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -785,9 +647,7 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -798,15 +658,11 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_39",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "taxes_and_charges_added",
@@ -816,9 +672,7 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -828,9 +682,7 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -838,18 +690,14 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "discount_section",
"fieldtype": "Section Break",
- "label": "Additional Discount",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Additional Discount"
},
{
"default": "Grand Total",
@@ -857,9 +705,7 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "base_discount_amount",
@@ -867,38 +713,28 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_45",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "totals_section",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "base_grand_total",
@@ -909,9 +745,7 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_rounding_adjustment",
@@ -920,21 +754,18 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"description": "In Words will be visible once you save the Purchase Order.",
"fieldname": "base_in_words",
"fieldtype": "Data",
"label": "In Words (Company Currency)",
+ "length": 240,
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_rounded_total",
@@ -944,16 +775,12 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break4",
"fieldtype": "Column Break",
- "oldfieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Column Break"
},
{
"fieldname": "grand_total",
@@ -963,9 +790,7 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "rounding_adjustment",
@@ -974,37 +799,30 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "rounded_total",
"fieldtype": "Currency",
"label": "Rounded Total",
"options": "currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"default": "0",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Disable Rounded Total"
},
{
"fieldname": "in_words",
"fieldtype": "Data",
"label": "In Words",
+ "length": 240,
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "advance_paid",
@@ -1013,25 +831,19 @@
"no_copy": 1,
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
- "label": "Payment Terms",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Payment Terms"
},
{
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"label": "Payment Terms Template",
- "options": "Payment Terms Template",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Payment Terms Template"
},
{
"fieldname": "payment_schedule",
@@ -1039,9 +851,7 @@
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"collapsible": 1,
@@ -1050,9 +860,7 @@
"fieldtype": "Section Break",
"label": "Terms and Conditions",
"oldfieldtype": "Section Break",
- "options": "fa fa-legal",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-legal"
},
{
"fieldname": "tc_name",
@@ -1061,27 +869,21 @@
"oldfieldname": "tc_name",
"oldfieldtype": "Link",
"options": "Terms and Conditions",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
"label": "Terms and Conditions",
"oldfieldname": "terms",
- "oldfieldtype": "Text Editor",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Text Editor"
},
{
"collapsible": 1,
"fieldname": "more_info",
"fieldtype": "Section Break",
"label": "More Information",
- "oldfieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Section Break"
},
{
"default": "Draft",
@@ -1096,9 +898,7 @@
"print_hide": 1,
"read_only": 1,
"reqd": 1,
- "search_index": 1,
- "show_days": 1,
- "show_seconds": 1
+ "search_index": 1
},
{
"fieldname": "ref_sq",
@@ -1109,9 +909,7 @@
"oldfieldname": "ref_sq",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "party_account_currency",
@@ -1121,24 +919,18 @@
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "inter_company_order_reference",
"fieldtype": "Link",
"label": "Inter Company Order Reference",
"options": "Sales Order",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_74",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.__islocal",
@@ -1148,9 +940,7 @@
"label": "% Received",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"depends_on": "eval:!doc.__islocal",
@@ -1160,9 +950,7 @@
"label": "% Billed",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
@@ -1172,8 +960,6 @@
"oldfieldtype": "Column Break",
"print_hide": 1,
"print_width": "50%",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -1184,9 +970,7 @@
"oldfieldname": "letter_head",
"oldfieldtype": "Select",
"options": "Letter Head",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"allow_on_submit": 1,
@@ -1198,15 +982,11 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "report_hide": 1
},
{
"fieldname": "column_break_86",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
@@ -1214,25 +994,19 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
- "label": "Subscription Section",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Subscription Section"
},
{
"allow_on_submit": 1,
@@ -1240,9 +1014,7 @@
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"allow_on_submit": 1,
@@ -1250,15 +1022,11 @@
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break_97",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "auto_repeat",
@@ -1267,72 +1035,56 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
- "label": "Update Auto Repeat Reference",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Update Auto Repeat Reference"
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
- "options": "Tax Category",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Tax Category"
},
{
"depends_on": "supplied_items",
"fieldname": "set_reserve_warehouse",
"fieldtype": "Link",
"label": "Set Reserve Warehouse",
- "options": "Warehouse",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Warehouse"
},
{
"collapsible": 1,
"fieldname": "tracking_section",
"fieldtype": "Section Break",
- "label": "Tracking",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Tracking"
},
{
"fieldname": "column_break_75",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "billing_address",
"fieldtype": "Link",
"label": "Select Billing Address",
- "options": "Address",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Address"
},
{
"fieldname": "billing_address_display",
"fieldtype": "Small Text",
"label": "Billing Address",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2020-06-13 22:25:47.333850",
+ "modified": "2020-07-18 05:09:33.800633",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@@ -1376,12 +1128,6 @@
"read": 1,
"role": "Purchase Manager",
"write": 1
- },
- {
- "email": 1,
- "print": 1,
- "read": 1,
- "role": "Accounts User"
}
],
"search_fields": "status, transaction_date, supplier,grand_total",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index 455bd68..4a937f7 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -166,7 +166,8 @@
{ "fieldtype": "Select", "label": __("Supplier"),
"fieldname": "supplier",
"options": doc.suppliers.map(d => d.supplier),
- "reqd": 1 },
+ "reqd": 1,
+ "default": doc.suppliers.length === 1 ? doc.suppliers[0].supplier_name : "" },
{ "fieldtype": "Button", "label": __('Create Supplier Quotation'),
"fieldname": "make_supplier_quotation", "cssClass": "btn-primary" },
]
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index 97aa922..5cd8e6f 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -1,4 +1,5 @@
{
+ "actions": "",
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2016-02-25 01:24:07.224790",
@@ -28,7 +29,6 @@
"letter_head",
"more_info",
"status",
- "fiscal_year",
"column_break3",
"amended_from"
],
@@ -219,17 +219,6 @@
"search_index": 1
},
{
- "fieldname": "fiscal_year",
- "fieldtype": "Link",
- "label": "Fiscal Year",
- "oldfieldname": "fiscal_year",
- "oldfieldtype": "Select",
- "options": "Fiscal Year",
- "print_hide": 1,
- "reqd": 1,
- "search_index": 1
- },
- {
"fieldname": "column_break3",
"fieldtype": "Column Break"
},
@@ -245,7 +234,8 @@
],
"icon": "fa fa-shopping-cart",
"is_submittable": 1,
- "modified": "2019-09-24 15:08:32.750661",
+ "links": [],
+ "modified": "2020-06-25 14:37:21.140194",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index dfdb487..b54a585 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -51,7 +51,7 @@
def validate_email_id(self, args):
if not args.email_id:
- frappe.throw(_("Row {0}: For supplier {0} Email Address is required to send email").format(args.idx, args.supplier))
+ frappe.throw(_("Row {0}: For Supplier {0}, Email Address is Required to Send Email").format(args.idx, args.supplier))
def on_submit(self):
frappe.db.set(self, 'status', 'Submitted')
@@ -154,7 +154,7 @@
sender=sender,attachments = attachments, send_email=True,
doctype=self.doctype, name=self.name)["name"]
- frappe.msgprint(_("Email sent to supplier {0}").format(data.supplier))
+ frappe.msgprint(_("Email Sent to Supplier {0}").format(data.supplier))
def get_attachments(self):
attachments = [d.name for d in get_attachments(self.doctype, self.name)]
@@ -193,7 +193,7 @@
def check_portal_enabled(reference_doctype):
if not frappe.db.get_value('Portal Menu Item',
{'reference_doctype': reference_doctype}, 'enabled'):
- frappe.throw(_("Request for Quotation is disabled to access from portal, for more check portal settings."))
+ frappe.throw(_("The Access to Request for Quotation From Portal is Disabled. To Allow Access, Enable it in Portal Settings."))
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
@@ -206,6 +206,8 @@
})
return list_context
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select `tabContact`.name from `tabContact`, `tabDynamic Link`
where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s
@@ -259,7 +261,7 @@
sq_doc.flags.ignore_permissions = True
sq_doc.run_method("set_missing_values")
sq_doc.save()
- frappe.msgprint(_("Supplier Quotation {0} created").format(sq_doc.name))
+ frappe.msgprint(_("Supplier Quotation {0} Created").format(sq_doc.name))
return sq_doc.name
except Exception:
return None
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index 7db1516..660dcff 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -593,6 +593,7 @@
"fieldname": "base_in_words",
"fieldtype": "Data",
"label": "In Words (Company Currency)",
+ "length": 240,
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -642,6 +643,7 @@
"fieldname": "in_words",
"fieldtype": "Data",
"label": "In Words",
+ "length": 240,
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -803,7 +805,7 @@
"idx": 29,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-15 21:24:12.639482",
+ "modified": "2020-07-18 05:10:45.556792",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",
diff --git a/erpnext/buying/module_onboarding/buying/buying.json b/erpnext/buying/module_onboarding/buying/buying.json
index 6e4bbc9..887f85b 100644
--- a/erpnext/buying/module_onboarding/buying/buying.json
+++ b/erpnext/buying/module_onboarding/buying/buying.json
@@ -19,7 +19,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying",
"idx": 0,
"is_complete": 0,
- "modified": "2020-06-01 12:55:09.234944",
+ "modified": "2020-07-08 14:05:28.273641",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",
@@ -47,8 +47,7 @@
"step": "Buying Settings"
}
],
- "subtitle": "Products, Purchases, Analysis and more.",
+ "subtitle": "Products, Purchases, Analysis, and more.",
"success_message": "The Buying Module is all set up!",
- "title": "Let's Set Up the Buying Module.",
- "user_can_dismiss": 1
+ "title": "Let's Set Up the Buying Module."
}
\ No newline at end of file
diff --git a/erpnext/buying/number_card/active_suppliers/active_suppliers.json b/erpnext/buying/number_card/active_suppliers/active_suppliers.json
new file mode 100644
index 0000000..91d5b13
--- /dev/null
+++ b/erpnext/buying/number_card/active_suppliers/active_suppliers.json
@@ -0,0 +1,20 @@
+{
+ "creation": "2020-07-20 21:01:02.499689",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Supplier",
+ "filters_json": "[[\"Supplier\",\"disabled\",\"=\",\"0\"]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Active Suppliers",
+ "modified": "2020-07-22 12:48:23.295193",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Active Suppliers",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/buying/number_card/annual_purchase/annual_purchase.json b/erpnext/buying/number_card/annual_purchase/annual_purchase.json
new file mode 100644
index 0000000..79f1b65
--- /dev/null
+++ b/erpnext/buying/number_card/annual_purchase/annual_purchase.json
@@ -0,0 +1,22 @@
+{
+ "aggregate_function_based_on": "base_net_total",
+ "creation": "2020-07-20 21:01:02.367127",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Purchase Order",
+ "dynamic_filters_json": "[[\"Purchase Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Purchase Order\",\"transaction_date\",\"Timespan\",\"this year\",false],[\"Purchase Order\",\"status\",\"not in\",[\"Draft\",\"Cancelled\",\"Closed\",null],false],[\"Purchase Order\",\"docstatus\",\"=\",\"1\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Annual Purchase",
+ "modified": "2020-07-22 21:21:58.755188",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Annual Purchase",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/buying/number_card/purchase_orders_to_bill/purchase_orders_to_bill.json b/erpnext/buying/number_card/purchase_orders_to_bill/purchase_orders_to_bill.json
new file mode 100644
index 0000000..44a0456
--- /dev/null
+++ b/erpnext/buying/number_card/purchase_orders_to_bill/purchase_orders_to_bill.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-20 21:01:02.468514",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Purchase Order",
+ "dynamic_filters_json": "[[\"Purchase Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Purchase Order\",\"status\",\"in\",[\"To Receive and Bill\",\"To Bill\",null],false],[\"Purchase Order\",\"docstatus\",\"=\",1,false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Purchase Orders to Bill",
+ "modified": "2020-07-22 12:48:10.300711",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Purchase Orders to Bill",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/buying/number_card/purchase_orders_to_receive/purchase_orders_to_receive.json b/erpnext/buying/number_card/purchase_orders_to_receive/purchase_orders_to_receive.json
new file mode 100644
index 0000000..442dab4
--- /dev/null
+++ b/erpnext/buying/number_card/purchase_orders_to_receive/purchase_orders_to_receive.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-20 21:01:02.438012",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Purchase Order",
+ "dynamic_filters_json": "[[\"Purchase Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Purchase Order\",\"status\",\"in\",[\"To Receive and Bill\",\"To Receive\",null],false],[\"Purchase Order\",\"docstatus\",\"=\",1,false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Purchase Orders to Receive",
+ "modified": "2020-07-22 12:47:47.460080",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Purchase Orders to Receive",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/buying_settings/buying_settings.json b/erpnext/buying/onboarding_step/buying_settings/buying_settings.json
index 6d765af..a788ccd 100644
--- a/erpnext/buying/onboarding_step/buying_settings/buying_settings.json
+++ b/erpnext/buying/onboarding_step/buying_settings/buying_settings.json
@@ -1,19 +1,19 @@
{
- "action": "Show Form Tour",
+ "action": "Update Settings",
"creation": "2020-05-06 15:53:44.667414",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 1,
- "is_single": 1,
+ "is_mandatory": 0,
+ "is_single": 0,
"is_skipped": 0,
- "modified": "2020-06-01 12:52:57.668870",
+ "modified": "2020-05-12 18:30:06.323797",
"modified_by": "Administrator",
"name": "Buying Settings",
"owner": "Administrator",
"reference_document": "Buying Settings",
"show_full_form": 0,
"title": "Configure Buying Settings.",
- "validate_action": 0
+ "validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
index 557c905..9457dee 100644
--- a/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
+++ b/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
@@ -8,13 +8,13 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-19 18:54:19.383397",
+ "modified": "2020-07-04 12:33:16.970031",
"modified_by": "Administrator",
"name": "Setup your Warehouse",
"owner": "Administrator",
"path": "Tree/Warehouse",
"reference_document": "Warehouse",
"show_full_form": 0,
- "title": "Setup your Warehouse",
+ "title": "Set up your Warehouse",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
index c7204a1..44ab767 100644
--- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
@@ -67,4 +67,5 @@
"expected_delivery_date": date_obj,
"actual_delivery_date": date_obj
}
+
return expected_data
\ No newline at end of file
diff --git a/erpnext/buying/report/requested_items_to_order/__init__.py b/erpnext/buying/report/requested_items_to_order_and_receive/__init__.py
similarity index 100%
rename from erpnext/buying/report/requested_items_to_order/__init__.py
rename to erpnext/buying/report/requested_items_to_order_and_receive/__init__.py
diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js
similarity index 95%
rename from erpnext/buying/report/requested_items_to_order/requested_items_to_order.js
rename to erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js
index 9555e82..d727584 100644
--- a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js
@@ -2,7 +2,7 @@
// For license information, please see license.txt
/* eslint-disable */
-frappe.query_reports["Requested Items to Order"] = {
+frappe.query_reports["Requested Items to Order and Receive"] = {
"filters": [
{
"fieldname": "company",
diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.json
similarity index 71%
rename from erpnext/buying/report/requested_items_to_order/requested_items_to_order.json
rename to erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.json
index 4a0578b..cb158f5 100644
--- a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.json
@@ -1,21 +1,21 @@
{
"add_total_row": 1,
- "creation": "2020-05-04 20:23:57.750719",
+ "creation": "2020-07-10 14:28:21.041310",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
- "modified": "2020-05-05 13:05:51.723951",
+ "modified": "2020-07-10 14:28:21.041310",
"modified_by": "Administrator",
"module": "Buying",
- "name": "Requested Items to Order",
+ "name": "Requested Items to Order and Receive",
"owner": "Administrator",
"prepared_report": 0,
"query": "",
"ref_doctype": "Material Request",
- "report_name": "Requested Items to Order",
+ "report_name": "Requested Items to Order and Receive",
"report_type": "Script Report",
"roles": [
{
diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
similarity index 80%
rename from erpnext/buying/report/requested_items_to_order/requested_items_to_order.py
rename to erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
index cca01b1..faf67c9 100644
--- a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
@@ -59,8 +59,11 @@
sum(ifnull(mr_item.stock_qty, 0)) as qty,
ifnull(mr_item.stock_uom, '') as uom,
sum(ifnull(mr_item.ordered_qty, 0)) as ordered_qty,
- (sum(mr_item.stock_qty) - sum(ifnull(mr_item.ordered_qty, 0))) as qty_to_order,
+ sum(ifnull(mr_item.received_qty, 0)) as received_qty,
+ (sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.received_qty, 0))) as qty_to_receive,
+ (sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.ordered_qty, 0))) as qty_to_order,
mr_item.item_name as item_name,
+ mr_item.description as "description",
mr.company as company
from
`tabMaterial Request` mr, `tabMaterial Request Item` mr_item
@@ -78,7 +81,7 @@
return data
def update_qty_columns(row_to_update, data_row):
- fields = ["qty", "ordered_qty", "qty_to_order"]
+ fields = ["qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"]
for field in fields:
row_to_update[field] += flt(data_row[field])
@@ -92,7 +95,9 @@
item_qty_map[row["item_code"]] = {
"qty" : row["qty"],
"ordered_qty" : row["ordered_qty"],
- "qty_to_order" : row["qty_to_order"]
+ "received_qty": row["received_qty"],
+ "qty_to_receive": row["qty_to_receive"],
+ "qty_to_order" : row["qty_to_order"],
}
else:
item_entry = item_qty_map[row["item_code"]]
@@ -122,7 +127,7 @@
return data, chart_data
def prepare_chart_data(item_data):
- labels, qty_to_order, ordered_qty = [], [], []
+ labels, qty_to_order, ordered_qty, received_qty, qty_to_receive = [], [], [], [], []
if len(item_data) > 30:
item_data = dict(list(item_data.items())[:30])
@@ -132,6 +137,8 @@
labels.append(row)
qty_to_order.append(mr_row["qty_to_order"])
ordered_qty.append(mr_row["ordered_qty"])
+ received_qty.append(mr_row["received_qty"])
+ qty_to_receive.append(mr_row["qty_to_receive"])
chart_data = {
"data" : {
@@ -144,6 +151,14 @@
{
'name': _('Ordered Qty'),
'values': ordered_qty
+ },
+ {
+ 'name': _('Received Qty'),
+ 'values': received_qty
+ },
+ {
+ 'name': _('Qty to Receive'),
+ 'values': qty_to_receive
}
]
},
@@ -193,7 +208,13 @@
"width": 100
},
{
- "label": _("UOM"),
+ "label": _("Description"),
+ "fieldname": "description",
+ "fieldtype": "Data",
+ "width": 200
+ },
+ {
+ "label": _("Stock UOM"),
"fieldname": "uom",
"fieldtype": "Data",
"width": 100,
@@ -201,7 +222,7 @@
columns.extend([
{
- "label": _("Qty"),
+ "label": _("Stock Qty"),
"fieldname": "qty",
"fieldtype": "Float",
"width": 120,
@@ -215,6 +236,20 @@
"convertible": "qty"
},
{
+ "label": _("Received Qty"),
+ "fieldname": "received_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Qty to Receive"),
+ "fieldname": "qty_to_receive",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
"label": _("Qty to Order"),
"fieldname": "qty_to_order",
"fieldtype": "Float",
diff --git a/erpnext/change_log/v13/v13_0_0-beta_4.md b/erpnext/change_log/v13/v13_0_0-beta_4.md
new file mode 100644
index 0000000..b835cec
--- /dev/null
+++ b/erpnext/change_log/v13/v13_0_0-beta_4.md
@@ -0,0 +1,72 @@
+### Version 13.0.0 Beta 4 Release Notes
+
+#### Features and Enhancements
+- New and refreshed POS ([#20789](https://github.com/frappe/erpnext/pull/20789))
+- Introduced Dunning ([#22559](https://github.com/frappe/erpnext/pull/22559))
+- Taxjar Integration ([#21047](https://github.com/frappe/erpnext/pull/21047))
+- Patient Progress Page ([#22474](https://github.com/frappe/erpnext/pull/22474))
+- Provision to make RFQ against Opportunity ([#22765](https://github.com/frappe/erpnext/pull/22765))
+- Added form dashboards and refactored custom buttons in Education module ([#22727](https://github.com/frappe/erpnext/pull/22727))
+- Student Attendance and Leave Enhancements ([#22623](https://github.com/frappe/erpnext/pull/22623))
+- Recruitment analytics ([#21732](https://github.com/frappe/erpnext/pull/21732))
+- Add medical coding fields to Healthcare DocTypes ([#22501](https://github.com/frappe/erpnext/pull/22501))
+- Autofill Supplier pop-up when only 1 Supplier in RFQ ([#22512](https://github.com/frappe/erpnext/pull/22512))
+- Accounting entries for service item in Purchase receipt ([#22223](https://github.com/frappe/erpnext/pull/22223))
+- Enhancement in subscription ([#22263](https://github.com/frappe/erpnext/pull/22263))
+- Laboratory Module Enhancements ([#22416](https://github.com/frappe/erpnext/pull/22416))
+- Added columns to get complete analysis for material request ([#22607](https://github.com/frappe/erpnext/pull/22607))
+- Added all companies option in employee tree to view employee across all companies ([#22573](https://github.com/frappe/erpnext/pull/22573))
+- Email Group Option In Email Campaign ([#22731](https://github.com/frappe/erpnext/pull/22731))
+- Added range for age in stock ageing ([#22622](https://github.com/frappe/erpnext/pull/22622))
+- Refactored shopping cart ([#22617](https://github.com/frappe/erpnext/pull/22617))
+
+#### Fixes:
+- Enable show_configure_button when shopping cart is enabled ([#22468](https://github.com/frappe/erpnext/pull/22468))
+- Setup status indicators for Job Offer and Job Applicant (develop) ([#22445](https://github.com/frappe/erpnext/pull/22445))
+- Item-wise sales history report ([#22783](https://github.com/frappe/erpnext/pull/22783))
+- Setting filter for project in kanban board ([#22717](https://github.com/frappe/erpnext/pull/22717))
+- Dashboard For Timesheet ([#22750](https://github.com/frappe/erpnext/pull/22750))
+- Handle custom statuses for the pause SLA configuration ([#22349](https://github.com/frappe/erpnext/pull/22349))
+- Quality Feedback and Template ([#22571](https://github.com/frappe/erpnext/pull/22571))
+- Unable to change link from new lead to existing customer ([#22787](https://github.com/frappe/erpnext/pull/22787))
+- Job applicant fixes ([#22448](https://github.com/frappe/erpnext/pull/22448))
+- Move Issue List actions under 'Actions' dropdown (ux) ([#22710](https://github.com/frappe/erpnext/pull/22710))
+- Cost center should only show option of selected company ([#22598](https://github.com/frappe/erpnext/pull/22598))
+- Serial No Rename does not affect Stock Ledger Entry ([#22746](https://github.com/frappe/erpnext/pull/22746))
+- Descriptions not copied while creating Fees from Fee Structure ([#22792](https://github.com/frappe/erpnext/pull/22792))
+- Company filter for cost_center and expense_account in all sales and purchase transactions ([#22478](https://github.com/frappe/erpnext/pull/22478))
+- Arrangements of filters for reports accounts payable & receivable ([#22636](https://github.com/frappe/erpnext/pull/22636))
+- Update the project after task deletion so that the % completed shows correct value ([#22591](https://github.com/frappe/erpnext/pull/22591))
+- Block Invalid Serial No updates in Maintenance Schedule ([#22665](https://github.com/frappe/erpnext/pull/22665))
+- Fetch item price in sales invoice based on it's validity ([#22563](https://github.com/frappe/erpnext/pull/22563))
+- Add view ledger button for cancelled docs ([#22432](https://github.com/frappe/erpnext/pull/22432))
+- Allow creating SLA documents even if SLA tracking is not enabled ([#22608](https://github.com/frappe/erpnext/pull/22608))
+- Quotation list view blank if quotation_to field not set as a standard filter ([#22672](https://github.com/frappe/erpnext/pull/22672))
+- Salary deductions report fixes ([#22397](https://github.com/frappe/erpnext/pull/22397))
+22727))
+- Incorrect delivered qty in Supplier-Wise Sales Analytics ([#22631](https://github.com/frappe/erpnext/pull/22631))
+- Moved parent warehouse to top section also added a section break ([#22708](https://github.com/frappe/erpnext/pull/22708))
+- Skip Progress and Completed by fields on Task Duplication ([#22565](https://github.com/frappe/erpnext/pull/22565))
+- Incorrect stock after merging the items ([#22526](https://github.com/frappe/erpnext/pull/22526))
+- Letter head not found in opening invoice creation tool ([#22488](https://github.com/frappe/erpnext/pull/22488))
+- Cannot cancel asset and asset movement ([#22441](https://github.com/frappe/erpnext/pull/22441))
+- Fetch project-related info in Timesheet ([#22423](https://github.com/frappe/erpnext/pull/22423))
+- Currency symbol not showing as per company currency in stock balance report ([#22724](https://github.com/frappe/erpnext/pull/22724))
+- Add default cost center in payment reconciliation JV ([#22614](https://github.com/frappe/erpnext/pull/22614))
+- Stock Reconciliation Invalid Quantity for Batched Item ([#22726](https://github.com/frappe/erpnext/pull/22726))
+- Project link not set in accounts other than profit and loss accounts ([#22051](https://github.com/frappe/erpnext/pull/22051))
+- Buying price for non stock item in gross profit report ([#22616](https://github.com/frappe/erpnext/pull/22616))
+- Heatmap in Vehicle ([#22743](https://github.com/frappe/erpnext/pull/22743))
+- Added Project Field in Purchase Receipt for Stock Ledger Tagging ([#22666](https://github.com/frappe/erpnext/pull/22666))
+- Multi currency payment reconciliation ([#22738](https://github.com/frappe/erpnext/pull/22738))
+- Cannot cancel assets with repair pending ([#22440](https://github.com/frappe/erpnext/pull/22440))
+- Reset homepage to home after unchecking products page ([#22736](https://github.com/frappe/erpnext/pull/22736))
+- Add project filter in parent task field ([#22655](https://github.com/frappe/erpnext/pull/22655))
+- Generic Message in previous doc validation for buying and selling ([#22546](https://github.com/frappe/erpnext/pull/22546))
+- Cess amount in GSTR 3B report ([#22701](https://github.com/frappe/erpnext/pull/22701))
+- Expense claim outstanding while making payment entry ([#22735](https://github.com/frappe/erpnext/pull/22735))
+- Take parent cost center for child if no cost center at child in expense claim ([#22496](https://github.com/frappe/erpnext/pull/22496))
+- Consider company fiscal year for getting balance ([#22577](https://github.com/frappe/erpnext/pull/22577))
+- Pick List empty table and Serial-Batch items handling ([#22426](https://github.com/frappe/erpnext/pull/22426))
+- Show total row in print format of financial statement ([#22693](https://github.com/frappe/erpnext/pull/22693))
+- Set Root as Parent if no parent in new tree view node ([#22497](https://github.com/frappe/erpnext/pull/22497))
\ No newline at end of file
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index ead503e..89c38c7 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1014,6 +1014,7 @@
def get_advance_payment_entries(party_type, party, party_account, order_doctype,
order_list=None, include_unallocated=True, against_all_orders=False, limit=None):
party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
+ currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
payment_type = "Receive" if party_type == "Customer" else "Pay"
payment_entries_against_order, unallocated_payment_entries = [], []
limit_cond = "limit %s" % limit if limit else ""
@@ -1030,14 +1031,15 @@
select
"Payment Entry" as reference_type, t1.name as reference_name,
t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
- t2.reference_name as against_order, t1.posting_date
+ t2.reference_name as against_order, t1.posting_date,
+ t1.{0} as currency
from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
where
- t1.name = t2.parent and t1.{0} = %s and t1.payment_type = %s
+ t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
- and t2.reference_doctype = %s {1}
- order by t1.posting_date {2}
- """.format(party_account_field, reference_condition, limit_cond),
+ and t2.reference_doctype = %s {2}
+ order by t1.posting_date {3}
+ """.format(currency_field, party_account_field, reference_condition, limit_cond),
[party_account, payment_type, party_type, party,
order_doctype] + order_list, as_dict=1)
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 9bba71d..babc5bd 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -10,7 +10,9 @@
from erpnext.stock.get_item_details import _get_item_tax_template
from frappe.utils import unique
- # searches for active employees
+# searches for active employees
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def employee_query(doctype, txt, searchfield, start, page_len, filters):
conditions = []
fields = get_fields("Employee", ["name", "employee_name"])
@@ -40,6 +42,8 @@
# searches for leads which are not converted
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def lead_query(doctype, txt, searchfield, start, page_len, filters):
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
@@ -69,6 +73,8 @@
# searches for customer
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def customer_query(doctype, txt, searchfield, start, page_len, filters):
conditions = []
cust_master_name = frappe.defaults.get_user_default("cust_master_name")
@@ -106,8 +112,11 @@
# searches for supplier
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
+
if supp_master_name == "Supplier Name":
fields = ["name", "supplier_group"]
else:
@@ -137,31 +146,50 @@
})
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
company_currency = erpnext.get_company_currency(filters.get('company'))
- tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount
- where tabAccount.docstatus!=2
- and account_type in (%s)
- and is_group = 0
- and company = %s
- and account_currency = %s
- and `%s` LIKE %s
- order by idx desc, name
- limit %s, %s""" %
- (", ".join(['%s']*len(filters.get("account_type"))), "%s", "%s", searchfield, "%s", "%s", "%s"),
- tuple(filters.get("account_type") + [filters.get("company"), company_currency, "%%%s%%" % txt,
- start, page_len]))
+ def get_accounts(with_account_type_filter):
+ account_type_condition = ''
+ if with_account_type_filter:
+ account_type_condition = "AND account_type in %(account_types)s"
+
+ accounts = frappe.db.sql("""
+ SELECT name, parent_account
+ FROM `tabAccount`
+ WHERE `tabAccount`.docstatus!=2
+ {account_type_condition}
+ AND is_group = 0
+ AND company = %(company)s
+ AND account_currency = %(currency)s
+ AND `{searchfield}` LIKE %(txt)s
+ ORDER BY idx DESC, name
+ LIMIT %(offset)s, %(limit)s
+ """.format(account_type_condition=account_type_condition, searchfield=searchfield),
+ dict(
+ account_types=filters.get("account_type"),
+ company=filters.get("company"),
+ currency=company_currency,
+ txt="%{}%".format(txt),
+ offset=start,
+ limit=page_len
+ )
+ )
+
+ return accounts
+
+ tax_accounts = get_accounts(True)
+
if not tax_accounts:
- tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount
- where tabAccount.docstatus!=2 and is_group = 0
- and company = %s and account_currency = %s and `%s` LIKE %s limit %s, %s""" #nosec
- % ("%s", "%s", searchfield, "%s", "%s", "%s"),
- (filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len))
+ tax_accounts = get_accounts(False)
return tax_accounts
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
conditions = []
@@ -209,7 +237,6 @@
idx desc,
name, item_name
limit %(start)s, %(page_len)s """.format(
- key=searchfield,
columns=columns,
scond=searchfields,
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
@@ -224,6 +251,8 @@
}, as_dict=as_dict)
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def bom(doctype, txt, searchfield, start, page_len, filters):
conditions = []
fields = get_fields("BOM", ["name", "item"])
@@ -250,6 +279,8 @@
})
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
cond = ''
if filters.get('customer'):
@@ -276,6 +307,8 @@
})
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
@@ -305,6 +338,8 @@
}, {"txt": ("%%%s%%" % txt)}, as_dict=as_dict)
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
cond = ""
if filters.get("posting_date"):
@@ -362,6 +397,8 @@
limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args)
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
filter_list = []
@@ -384,20 +421,8 @@
fields = ["name", "parent_account"],
limit_start=start, limit_page_length=page_len, as_list=True)
-def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date
- from `tabBlanket Order` bo, `tabBlanket Order Item` boi
- where
- boi.parent = bo.name
- and boi.item_code = {item_code}
- and bo.blanket_order_type = '{blanket_order_type}'
- and bo.company = {company}
- and bo.docstatus = 1"""
- .format(item_code = frappe.db.escape(filters.get("item")),
- blanket_order_type = filters.get("blanket_order_type"),
- company = frappe.db.escape(filters.get("company"))
- ))
-
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
@@ -414,6 +439,7 @@
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_income_account(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
@@ -440,6 +466,7 @@
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
@@ -464,6 +491,7 @@
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
# Should be used when item code is passed in filters.
conditions, bin_conditions = [], []
@@ -507,6 +535,7 @@
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
query = """select batch_id from `tabBatch`
where disabled = 0
@@ -520,6 +549,7 @@
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters):
item_filters = [
['manufacturer', 'like', '%' + txt + '%'],
@@ -538,6 +568,7 @@
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
query = """
select pr.name
@@ -552,6 +583,7 @@
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
query = """
select pi.name
@@ -566,6 +598,7 @@
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
item_doc = frappe.get_cached_doc('Item', filters.get('item_code'))
@@ -583,7 +616,8 @@
args = {
'item_code': filters.get('item_code'),
'posting_date': filters.get('valid_from'),
- 'tax_category': filters.get('tax_category')
+ 'tax_category': filters.get('tax_category'),
+ 'company': filters.get('company')
}
taxes = _get_item_tax_template(args, taxes, for_validate=True)
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 90c67f1..3f127a2 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -213,7 +213,7 @@
doc.return_against = source.name
doc.ignore_pricing_rule = 1
doc.set_warehouse = ""
- if doctype == "Sales Invoice":
+ if doctype == "Sales Invoice" or doctype == "POS Invoice":
doc.is_pos = source.is_pos
# look for Print Heading "Credit Note"
@@ -229,7 +229,7 @@
tax.tax_amount = -1 * tax.tax_amount
if doc.get("is_return"):
- if doc.doctype == 'Sales Invoice':
+ if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice':
doc.set('payments', [])
for data in source.payments:
paid_amount = 0.00
@@ -241,8 +241,11 @@
'mode_of_payment': data.mode_of_payment,
'type': data.type,
'amount': -1 * paid_amount,
- 'base_amount': -1 * base_paid_amount
+ 'base_amount': -1 * base_paid_amount,
+ 'account': data.account
})
+ if doc.is_pos:
+ doc.paid_amount = -1 * source.paid_amount
elif doc.doctype == 'Purchase Invoice':
doc.paid_amount = -1 * source.paid_amount
doc.base_paid_amount = -1 * source.base_paid_amount
@@ -287,7 +290,7 @@
target_doc.dn_detail = source_doc.name
if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return
- elif doctype == "Sales Invoice":
+ elif doctype == "Sales Invoice" or doctype == "POS Invoice":
target_doc.sales_order = source_doc.sales_order
target_doc.delivery_note = source_doc.delivery_note
target_doc.so_detail = source_doc.so_detail
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index b465a10..0dc9878 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -85,6 +85,12 @@
"Bank Transaction": [
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],
["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"]
+ ],
+ "POS Opening Entry": [
+ ["Draft", None],
+ ["Open", "eval:self.docstatus == 1 and not self.pos_closing_entry"],
+ ["Closed", "eval:self.docstatus == 1 and self.pos_closing_entry"],
+ ["Cancelled", "eval:self.docstatus == 2"],
]
}
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 6af6fee..2017f16 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -96,6 +96,7 @@
"account": warehouse_account[sle.warehouse]["account"],
"against": item_row.expense_account,
"cost_center": item_row.cost_center,
+ "project": item_row.project or self.get('project'),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"debit": flt(sle.stock_value_difference, precision),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
@@ -106,6 +107,7 @@
"account": item_row.expense_account,
"against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center,
+ "project": item_row.project or self.get('project'),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"credit": flt(sle.stock_value_difference, precision),
"project": item_row.get("project") or self.get("project"),
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 4e568e2..572e1ca 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -53,7 +53,8 @@
'tax_category': self.doc.get('tax_category'),
'posting_date': self.doc.get('posting_date'),
'bill_date': self.doc.get('bill_date'),
- 'transaction_date': self.doc.get('transaction_date')
+ 'transaction_date': self.doc.get('transaction_date'),
+ 'company': self.doc.get('company')
}
item_group = item_doc.item_group
@@ -369,7 +370,7 @@
self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
- if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
+ if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \
if self.doc.total_taxes_and_charges else self.doc.base_net_total
else:
@@ -596,7 +597,7 @@
base_rate_with_margin = 0.0
if item.price_list_rate:
if item.pricing_rules and not self.doc.ignore_pricing_rule:
- for d in item.pricing_rules.split(','):
+ for d in json.loads(item.pricing_rules):
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
@@ -618,17 +619,14 @@
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
def update_paid_amount_for_return(self, total_amount_to_pay):
- default_mode_of_payment = frappe.db.get_value('Sales Invoice Payment',
- {'parent': self.doc.pos_profile, 'default': 1},
- ['mode_of_payment', 'type', 'account'], as_dict=1)
+ default_mode_of_payment = frappe.db.get_value('POS Payment Method',
+ {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
self.doc.payments = []
if default_mode_of_payment:
self.doc.append('payments', {
'mode_of_payment': default_mode_of_payment.mode_of_payment,
- 'type': default_mode_of_payment.type,
- 'account': default_mode_of_payment.account,
'amount': total_amount_to_pay
})
else:
diff --git a/erpnext/controllers/tests/test_mapper.py b/erpnext/controllers/tests/test_mapper.py
index 8839e00..66459fd 100644
--- a/erpnext/controllers/tests/test_mapper.py
+++ b/erpnext/controllers/tests/test_mapper.py
@@ -13,14 +13,12 @@
'''Test mapping of multiple source docs on a single target doc'''
make_test_records("Item")
- items = frappe.get_all("Item", fields = ["name", "item_code"], filters = {'is_sales_item': 1, 'has_variants': 0, 'disabled': 0})
- customers = frappe.get_all("Customer")
- if items and customers:
- # Make source docs (quotations) and a target doc (sales order)
- customer = random.choice(customers).name
- qtn1, item_list_1 = self.make_quotation(items, customer)
- qtn2, item_list_2 = self.make_quotation(items, customer)
- so, item_list_3 = self.make_sales_order()
+ items = ['_Test Item', '_Test Item 2', '_Test FG Item']
+
+ # Make source docs (quotations) and a target doc (sales order)
+ qtn1, item_list_1 = self.make_quotation(items, '_Test Customer')
+ qtn2, item_list_2 = self.make_quotation(items, '_Test Customer')
+ so, item_list_3 = self.make_sales_order()
# Map source docs to target with corresponding mapper method
method = "erpnext.selling.doctype.quotation.quotation.make_sales_order"
@@ -28,18 +26,12 @@
# Assert that all inserted items are present in updated sales order
src_items = item_list_1 + item_list_2 + item_list_3
- self.assertEqual(set([d.item_code for d in src_items]),
+ self.assertEqual(set([d for d in src_items]),
set([d.item_code for d in updated_so.items]))
- def get_random_items(self, items, limit):
- '''Get a number of random items from a list of given items'''
- random_items = []
- for i in range(0, limit):
- random_items.append(random.choice(items))
- return random_items
- def make_quotation(self, items, customer):
- item_list = self.get_random_items(items, 3)
+ def make_quotation(self, item_list, customer):
+
qtn = frappe.get_doc({
"doctype": "Quotation",
"quotation_to": "Customer",
@@ -49,7 +41,7 @@
"valid_till" : add_months(nowdate(), 1)
})
for item in item_list:
- qtn.append("items", {"qty": "2", "item_code": item.item_code})
+ qtn.append("items", {"qty": "2", "item_code": item})
qtn.submit()
return qtn, item_list
@@ -60,7 +52,7 @@
"base_rate": 100.0,
"description": "CPU",
"doctype": "Sales Order Item",
- "item_code": "_Test Item Home Desktop 100",
+ "item_code": "_Test Item",
"item_name": "CPU",
"parentfield": "items",
"qty": 10.0,
@@ -72,4 +64,4 @@
})
so = frappe.get_doc(frappe.get_test_records('Sales Order')[0])
so.insert(ignore_permissions=True)
- return so, [item]
+ return so, [item.item_code]
diff --git a/erpnext/controllers/tests/test_qty_based_taxes.py b/erpnext/controllers/tests/test_qty_based_taxes.py
index fd9936b..aaeac5d 100644
--- a/erpnext/controllers/tests/test_qty_based_taxes.py
+++ b/erpnext/controllers/tests/test_qty_based_taxes.py
@@ -30,6 +30,7 @@
self.item_tax_template = frappe.get_doc({
'doctype': 'Item Tax Template',
'title': uuid4(),
+ 'company': self.company.name,
'taxes': [
{
'tax_type': self.account.name,
diff --git a/erpnext/controllers/trends.py b/erpnext/controllers/trends.py
index 092baa4..9b4b0eb 100644
--- a/erpnext/controllers/trends.py
+++ b/erpnext/controllers/trends.py
@@ -33,7 +33,7 @@
frappe.throw(_("{0} is mandatory").format(f))
if not frappe.db.exists("Fiscal Year", filters.get("fiscal_year")):
- frappe.throw(_("Fiscal Year: {0} does not exists").format(filters.get("fiscal_year")))
+ frappe.throw(_("Fiscal Year {0} Does Not Exist").format(filters.get("fiscal_year")))
if filters.get("based_on") == filters.get("group_by"):
frappe.throw(_("'Based On' and 'Group By' can not be same"))
diff --git a/erpnext/crm/crm_dashboard/crm/crm.json b/erpnext/crm/crm_dashboard/crm/crm.json
new file mode 100644
index 0000000..69c2c8a
--- /dev/null
+++ b/erpnext/crm/crm_dashboard/crm/crm.json
@@ -0,0 +1,58 @@
+{
+ "cards": [
+ {
+ "card": "New Lead (Last 1 Month)"
+ },
+ {
+ "card": "New Opportunity (Last 1 Month)"
+ },
+ {
+ "card": "Won Opportunity (Last 1 Month)"
+ },
+ {
+ "card": "Open Opportunity"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "Incoming Leads",
+ "width": "Full"
+ },
+ {
+ "chart": "Opportunity Trends",
+ "width": "Full"
+ },
+ {
+ "chart": "Won Opportunities",
+ "width": "Full"
+ },
+ {
+ "chart": "Territory Wise Opportunity Count",
+ "width": "Half"
+ },
+ {
+ "chart": "Opportunities via Campaigns",
+ "width": "Half"
+ },
+ {
+ "chart": "Territory Wise Sales",
+ "width": "Full"
+ },
+ {
+ "chart": "Lead Source",
+ "width": "Half"
+ }
+ ],
+ "creation": "2020-07-20 20:17:15.985657",
+ "dashboard_name": "CRM",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 0,
+ "is_standard": 1,
+ "modified": "2020-07-21 18:56:47.230053",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "CRM",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/crm/dashboard_chart/incoming_leads/incoming_leads.json b/erpnext/crm/dashboard_chart/incoming_leads/incoming_leads.json
new file mode 100644
index 0000000..82398eb
--- /dev/null
+++ b/erpnext/crm/dashboard_chart/incoming_leads/incoming_leads.json
@@ -0,0 +1,28 @@
+{
+ "based_on": "creation",
+ "chart_name": "Incoming Leads",
+ "chart_type": "Count",
+ "creation": "2020-07-20 20:17:15.639164",
+ "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Lead",
+ "dynamic_filters_json": "[[\"Lead\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[]",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 15:49:19.896501",
+ "modified": "2020-07-22 16:06:34.941729",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Incoming Leads",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Weekly",
+ "timeseries": 1,
+ "timespan": "Last Quarter",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/crm/dashboard_chart/lead_source/lead_source.json b/erpnext/crm/dashboard_chart/lead_source/lead_source.json
new file mode 100644
index 0000000..f25fea5
--- /dev/null
+++ b/erpnext/crm/dashboard_chart/lead_source/lead_source.json
@@ -0,0 +1,27 @@
+{
+ "chart_name": "Lead Source",
+ "chart_type": "Group By",
+ "creation": "2020-07-20 20:17:15.842106",
+ "custom_options": "{\"truncateLegends\": 1, \"maxSlices\": 8}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Lead",
+ "dynamic_filters_json": "[[\"Lead\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[]",
+ "group_by_based_on": "source",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 16:11:14.170636",
+ "modified": "2020-07-22 16:13:38.696710",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Lead Source",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Donut",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/crm/dashboard_chart/opportunities_via_campaigns/opportunities_via_campaigns.json b/erpnext/crm/dashboard_chart/opportunities_via_campaigns/opportunities_via_campaigns.json
new file mode 100644
index 0000000..4adda9a
--- /dev/null
+++ b/erpnext/crm/dashboard_chart/opportunities_via_campaigns/opportunities_via_campaigns.json
@@ -0,0 +1,27 @@
+{
+ "chart_name": "Opportunities via Campaigns",
+ "chart_type": "Group By",
+ "creation": "2020-07-20 20:17:15.705402",
+ "custom_options": "{\"truncateLegends\": 1, \"maxSlices\": 8}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Opportunity",
+ "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[]",
+ "group_by_based_on": "campaign",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 15:45:32.572011",
+ "modified": "2020-07-22 16:10:02.497726",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Opportunities via Campaigns",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Pie",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/crm/dashboard_chart/opportunity_trends/opportunity_trends.json b/erpnext/crm/dashboard_chart/opportunity_trends/opportunity_trends.json
new file mode 100644
index 0000000..08e26cd
--- /dev/null
+++ b/erpnext/crm/dashboard_chart/opportunity_trends/opportunity_trends.json
@@ -0,0 +1,28 @@
+{
+ "based_on": "creation",
+ "chart_name": "Opportunity Trends",
+ "chart_type": "Count",
+ "creation": "2020-07-20 20:17:15.672124",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Opportunity",
+ "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[]",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 15:45:32.590967",
+ "modified": "2020-07-22 16:08:33.100532",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Opportunity Trends",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Weekly",
+ "timeseries": 1,
+ "timespan": "Last Quarter",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/crm/dashboard_chart/territory_wise_opportunity_count/territory_wise_opportunity_count.json b/erpnext/crm/dashboard_chart/territory_wise_opportunity_count/territory_wise_opportunity_count.json
new file mode 100644
index 0000000..8b15ec9
--- /dev/null
+++ b/erpnext/crm/dashboard_chart/territory_wise_opportunity_count/territory_wise_opportunity_count.json
@@ -0,0 +1,27 @@
+{
+ "chart_name": "Territory Wise Opportunity Count",
+ "chart_type": "Group By",
+ "creation": "2020-07-20 20:17:15.774176",
+ "custom_options": "{\"truncateLegends\": 1, \"maxSlices\": 8}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Opportunity",
+ "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[]",
+ "group_by_based_on": "territory",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 15:45:32.134026",
+ "modified": "2020-07-22 16:09:42.921547",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Territory Wise Opportunity Count",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Donut",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/crm/dashboard_chart/territory_wise_sales/territory_wise_sales.json b/erpnext/crm/dashboard_chart/territory_wise_sales/territory_wise_sales.json
new file mode 100644
index 0000000..fe142b4
--- /dev/null
+++ b/erpnext/crm/dashboard_chart/territory_wise_sales/territory_wise_sales.json
@@ -0,0 +1,28 @@
+{
+ "aggregate_function_based_on": "opportunity_amount",
+ "chart_name": "Territory Wise Sales",
+ "chart_type": "Group By",
+ "creation": "2020-07-20 20:17:15.809008",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Opportunity",
+ "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Opportunity\",\"status\",\"=\",\"Converted\",false]]",
+ "group_by_based_on": "territory",
+ "group_by_type": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 15:45:32.501313",
+ "modified": "2020-07-22 16:10:28.308110",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Territory Wise Sales",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/crm/dashboard_chart/won_opportunities/won_opportunities.json b/erpnext/crm/dashboard_chart/won_opportunities/won_opportunities.json
new file mode 100644
index 0000000..2b5576b
--- /dev/null
+++ b/erpnext/crm/dashboard_chart/won_opportunities/won_opportunities.json
@@ -0,0 +1,27 @@
+{
+ "based_on": "modified",
+ "chart_name": "Won Opportunities",
+ "chart_type": "Count",
+ "creation": "2020-07-20 20:17:15.738889",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Opportunity",
+ "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Opportunity\",\"status\",\"=\",\"Converted\",false]]",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 15:45:32.575964",
+ "modified": "2020-07-22 16:09:14.265231",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Won Opportunities",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 1,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/crm/dashboard_fixtures.py b/erpnext/crm/dashboard_fixtures.py
deleted file mode 100644
index 0535cbb..0000000
--- a/erpnext/crm/dashboard_fixtures.py
+++ /dev/null
@@ -1,214 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe, erpnext, json
-from frappe import _
-
-def get_data():
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards()
- })
-
-def get_dashboards():
- return [{
- "doctype": "Dashboard",
- "name": "CRM",
- "dashboard_name": "CRM",
- "charts": [
- { "chart": "Incoming Leads", "width": "Full" },
- { "chart": "Opportunity Trends", "width": "Full"},
- { "chart": "Won Opportunities", "width": "Full" },
- { "chart": "Territory Wise Opportunity Count", "width": "Half"},
- { "chart": "Opportunities via Campaigns", "width": "Half" },
- { "chart": "Territory Wise Sales", "width": "Full"},
- { "chart": "Lead Source", "width": "Half"}
- ],
- "cards": [
- { "card": "New Lead (Last 1 Month)" },
- { "card": "New Opportunity (Last 1 Month)" },
- { "card": "Won Opportunity (Last 1 Month)" },
- { "card": "Open Opportunity"},
- ]
- }]
-
-def get_company_for_dashboards():
- company = frappe.defaults.get_defaults().company
- if company:
- return company
- else:
- company_list = frappe.get_list("Company")
- if company_list:
- return company_list[0].name
- return None
-
-def get_charts():
- company = get_company_for_dashboards()
-
- return [{
- "name": "Incoming Leads",
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Count",
- "chart_name": _("Incoming Leads"),
- "timespan": "Last Quarter",
- "time_interval": "Weekly",
- "document_type": "Lead",
- "based_on": "creation",
- 'is_public': 1,
- 'timeseries': 1,
- "owner": "Administrator",
- "filters_json": json.dumps([]),
- "type": "Bar"
- },
- {
- "name": "Opportunity Trends",
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Count",
- "chart_name": _("Opportunity Trends"),
- "timespan": "Last Quarter",
- "time_interval": "Weekly",
- "document_type": "Opportunity",
- "based_on": "creation",
- 'is_public': 1,
- 'timeseries': 1,
- "owner": "Administrator",
- "filters_json": json.dumps([["Opportunity", "company", "=", company, False]]),
- "type": "Bar"
- },
- {
- "name": "Opportunities via Campaigns",
- "chart_name": _("Opportunities via Campaigns"),
- "doctype": "Dashboard Chart",
- "chart_type": "Group By",
- "group_by_type": "Count",
- "group_by_based_on": "campaign",
- "document_type": "Opportunity",
- 'is_public': 1,
- 'timeseries': 1,
- "owner": "Administrator",
- "filters_json": json.dumps([["Opportunity", "company", "=", company, False]]),
- "type": "Pie",
- "custom_options": json.dumps({
- "truncateLegends": 1,
- "maxSlices": 8
- })
- },
- {
- "name": "Won Opportunities",
- "doctype": "Dashboard Chart",
- "time_interval": "Yearly",
- "chart_type": "Count",
- "chart_name": _("Won Opportunities"),
- "timespan": "Last Year",
- "time_interval": "Monthly",
- "document_type": "Opportunity",
- "based_on": "modified",
- 'is_public': 1,
- 'timeseries': 1,
- "owner": "Administrator",
- "filters_json": json.dumps([
- ["Opportunity", "company", "=", company, False],
- ["Opportunity", "status", "=", "Converted", False]]),
- "type": "Bar"
- },
- {
- "name": "Territory Wise Opportunity Count",
- "doctype": "Dashboard Chart",
- "chart_type": "Group By",
- "group_by_type": "Count",
- "group_by_based_on": "territory",
- "chart_name": _("Territory Wise Opportunity Count"),
- "document_type": "Opportunity",
- 'is_public': 1,
- "filters_json": json.dumps([
- ["Opportunity", "company", "=", company, False]
- ]),
- "owner": "Administrator",
- "type": "Donut",
- "custom_options": json.dumps({
- "truncateLegends": 1,
- "maxSlices": 8
- })
- },
- {
- "name": "Territory Wise Sales",
- "doctype": "Dashboard Chart",
- "chart_type": "Group By",
- "group_by_type": "Sum",
- "group_by_based_on": "territory",
- "chart_name": _("Territory Wise Sales"),
- "aggregate_function_based_on": "opportunity_amount",
- "document_type": "Opportunity",
- 'is_public': 1,
- "owner": "Administrator",
- "filters_json": json.dumps([
- ["Opportunity", "company", "=", company, False],
- ["Opportunity", "status", "=", "Converted", False]
- ]),
- "type": "Bar"
- },
- {
- "name": "Lead Source",
- "doctype": "Dashboard Chart",
- "chart_type": "Group By",
- "group_by_type": "Count",
- "group_by_based_on": "source",
- "chart_name": _("Lead Source"),
- "document_type": "Lead",
- 'is_public': 1,
- "owner": "Administrator",
- "type": "Pie",
- "custom_options": json.dumps({
- "truncateLegends": 1,
- "maxSlices": 8
- })
- }]
-
-def get_number_cards():
- return [{
- "doctype": "Number Card",
- "document_type": "Lead",
- "name": "New Lead (Last 1 Month)",
- "filters_json": json.dumps([["Lead","creation","Previous","1 month",False]]),
- "function": "Count",
- "is_public": 1,
- "label": _("New Lead (Last 1 Month)"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Daily"
- },
- {
- "doctype": "Number Card",
- "document_type": "Opportunity",
- "name": "New Opportunity (Last 1 Month)",
- "filters_json": json.dumps([["Opportunity","creation","Previous","1 month",False]]),
- "function": "Count",
- "is_public": 1,
- "label": _("New Opportunity (Last 1 Month)"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Daily"
- },
- {
- "doctype": "Number Card",
- "document_type": "Opportunity",
- "name": "Won Opportunity (Last 1 Month)",
- "filters_json": json.dumps([["Opportunity","creation","Previous","1 month",False]]),
- "function": "Count",
- "is_public": 1,
- "label": _("Won Opportunity (Last 1 Month)"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Daily"
- },
- {
- "doctype": "Number Card",
- "document_type": "Opportunity",
- "name": "Open Opportunity",
- "filters_json": json.dumps([["Opportunity","status","=","Open",False]]),
- "function": "Count",
- "is_public": 1,
- "label": _("Open Opportunity"),
- "show_percentage_stats": 1,
- "stats_time_interval": "Daily"
- }]
\ No newline at end of file
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.json b/erpnext/crm/doctype/email_campaign/email_campaign.json
index 736a9d6..0340364 100644
--- a/erpnext/crm/doctype/email_campaign/email_campaign.json
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "format:MAIL-CAMP-{YYYY}-{#####}",
"creation": "2019-06-30 16:05:30.015615",
"doctype": "DocType",
@@ -52,7 +53,7 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Email Campaign For ",
- "options": "\nLead\nContact",
+ "options": "\nLead\nContact\nEmail Group",
"reqd": 1
},
{
@@ -70,7 +71,8 @@
"options": "User"
}
],
- "modified": "2019-11-11 17:18:47.342839",
+ "links": [],
+ "modified": "2020-07-15 12:43:25.548682",
"modified_by": "Administrator",
"module": "CRM",
"name": "Email Campaign",
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py
index 8f60ecf..71c93e8 100644
--- a/erpnext/crm/doctype/email_campaign/email_campaign.py
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.py
@@ -70,10 +70,15 @@
send_mail(entry, email_campaign)
def send_mail(entry, email_campaign):
- recipient = frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), 'email_id')
+ recipient_list = []
+ if email_campaign.email_campaign_for == "Email Group":
+ for member in frappe.db.get_list("Email Group Member", filters={"email_group": email_campaign.get("recipient")}, fields=["email"]):
+ recipient_list.append(member['email'])
+ else:
+ recipient_list.append(frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), "email_id"))
email_template = frappe.get_doc("Email Template", entry.get("email_template"))
- sender = frappe.db.get_value("User", email_campaign.get("sender"), 'email')
+ sender = frappe.db.get_value("User", email_campaign.get("sender"), "email")
context = {"doc": frappe.get_doc(email_campaign.email_campaign_for, email_campaign.recipient)}
# send mail and link communication to document
comm = make(
@@ -82,7 +87,7 @@
subject = frappe.render_template(email_template.get("subject"), context),
content = frappe.render_template(email_template.get("response"), context),
sender = sender,
- recipients = recipient,
+ recipients = recipient_list,
communication_medium = "Email",
sent_or_received = "Sent",
send_email = True,
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index ec7d14d..315d298 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -114,10 +114,12 @@
def set_lead_name(self):
if not self.lead_name:
# Check for leads being created through data import
- if not self.company_name and not self.flags.ignore_mandatory:
+ if not self.company_name and not self.email_id and not self.flags.ignore_mandatory:
frappe.throw(_("A Lead requires either a person's name or an organization's name"))
-
- self.lead_name = self.company_name
+ elif self.company_name:
+ self.lead_name = self.company_name
+ else:
+ self.lead_name = self.email_id.split("@")[0]
def set_title(self):
if self.organization_lead:
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index f1b8171..08958b7 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -30,7 +30,6 @@
},
party_name: function(frm) {
- frm.toggle_display("contact_info", frm.doc.party_name);
frm.trigger('set_contact_link');
if (frm.doc.opportunity_from == "Customer") {
@@ -48,10 +47,6 @@
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
- with_items: function(frm) {
- frm.trigger('toggle_mandatory');
- },
-
customer_address: function(frm, cdt, cdn) {
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false);
},
@@ -59,15 +54,19 @@
contact_person: erpnext.utils.get_contact_details,
opportunity_from: function(frm) {
+ frm.trigger('setup_opportunity_from');
+
+ frm.set_value("party_name", "");
+ },
+
+ setup_opportunity_from: function(frm) {
frm.trigger('setup_queries');
- frm.toggle_reqd("party_name", frm.doc.opportunity_from);
frm.trigger("set_dynamic_field_label");
},
refresh: function(frm) {
var doc = frm.doc;
- frm.events.opportunity_from(frm);
- frm.trigger('toggle_mandatory');
+ frm.trigger('setup_opportunity_from');
erpnext.toggle_naming_series();
if(!doc.__islocal && doc.status!=="Lost") {
@@ -76,6 +75,11 @@
function() {
frm.trigger("make_supplier_quotation")
}, __('Create'));
+
+ frm.add_custom_button(__('Request For Quotation'),
+ function() {
+ frm.trigger("make_request_for_quotation")
+ }, __('Create'));
}
frm.add_custom_button(__('Quotation'),
@@ -113,7 +117,6 @@
},
set_dynamic_field_label: function(frm){
-
if (frm.doc.opportunity_from) {
frm.set_df_property("party_name", "label", frm.doc.opportunity_from);
}
@@ -122,13 +125,17 @@
make_supplier_quotation: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.opportunity.opportunity.make_supplier_quotation",
- frm: cur_frm
+ frm: frm
})
},
- toggle_mandatory: function(frm) {
- frm.toggle_reqd("items", frm.doc.with_items ? 1:0);
- }
+ make_request_for_quotation: function(frm) {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation",
+ frm: frm
+ })
+ },
+
})
// TODO commonify this code
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index 6a54c5f..545e232 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -254,6 +254,7 @@
"fieldname": "items",
"fieldtype": "Table",
"label": "Items",
+ "mandatory_depends_on": "eval: doc.with_items == 1",
"oldfieldname": "enquiry_details",
"oldfieldtype": "Table",
"options": "Opportunity Item"
@@ -423,7 +424,7 @@
"icon": "fa fa-info-sign",
"idx": 195,
"links": [],
- "modified": "2020-04-07 09:05:39.391109",
+ "modified": "2020-07-14 16:49:15.888503",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",
diff --git a/erpnext/crm/module_onboarding/crm/crm.json b/erpnext/crm/module_onboarding/crm/crm.json
index 44d672a..8315218 100644
--- a/erpnext/crm/module_onboarding/crm/crm.json
+++ b/erpnext/crm/module_onboarding/crm/crm.json
@@ -16,7 +16,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM",
"idx": 0,
"is_complete": 0,
- "modified": "2020-05-28 21:07:41.278784",
+ "modified": "2020-07-08 14:05:42.644448",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
@@ -35,8 +35,7 @@
"step": "Create and Send Quotation"
}
],
- "subtitle": "Lead, Opportunity, Customer and more.",
- "success_message": "CRM Module is all Set Up!",
- "title": "Let's Set Up Your CRM.",
- "user_can_dismiss": 1
+ "subtitle": "Lead, Opportunity, Customer, and more.",
+ "success_message": "The CRM Module is all set up!",
+ "title": "Let's Set Up Your CRM."
}
\ No newline at end of file
diff --git "a/erpnext/crm/number_card/new_lead_\050last_1_month\051/new_lead_\050last_1_month\051.json" "b/erpnext/crm/number_card/new_lead_\050last_1_month\051/new_lead_\050last_1_month\051.json"
new file mode 100644
index 0000000..4511f54
--- /dev/null
+++ "b/erpnext/crm/number_card/new_lead_\050last_1_month\051/new_lead_\050last_1_month\051.json"
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-20 20:17:15.870736",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Lead",
+ "dynamic_filters_json": "[[\"Lead\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Lead\",\"creation\",\"Timespan\",\"last month\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "New Lead (Last 1 Month)",
+ "modified": "2020-07-22 16:15:17.274972",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "New Lead (Last 1 Month)",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git "a/erpnext/crm/number_card/new_opportunity_\050last_1_month\051/new_opportunity_\050last_1_month\051.json" "b/erpnext/crm/number_card/new_opportunity_\050last_1_month\051/new_opportunity_\050last_1_month\051.json"
new file mode 100644
index 0000000..90997b7
--- /dev/null
+++ "b/erpnext/crm/number_card/new_opportunity_\050last_1_month\051/new_opportunity_\050last_1_month\051.json"
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-20 20:17:15.897112",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Opportunity",
+ "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Opportunity\",\"creation\",\"Timespan\",\"last month\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "New Opportunity (Last 1 Month)",
+ "modified": "2020-07-22 16:07:27.910432",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "New Opportunity (Last 1 Month)",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/crm/number_card/open_opportunity/open_opportunity.json b/erpnext/crm/number_card/open_opportunity/open_opportunity.json
new file mode 100644
index 0000000..6e06ed6
--- /dev/null
+++ b/erpnext/crm/number_card/open_opportunity/open_opportunity.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-20 20:17:15.948113",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Opportunity",
+ "dynamic_filters_json": "[[\"Opportunity\",\"status\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Opportunity\",\"company\",\"=\",null,false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Open Opportunity",
+ "modified": "2020-07-22 16:16:16.420446",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Open Opportunity",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git "a/erpnext/crm/number_card/won_opportunity_\050last_1_month\051/won_opportunity_\050last_1_month\051.json" "b/erpnext/crm/number_card/won_opportunity_\050last_1_month\051/won_opportunity_\050last_1_month\051.json"
new file mode 100644
index 0000000..ba0c07e
--- /dev/null
+++ "b/erpnext/crm/number_card/won_opportunity_\050last_1_month\051/won_opportunity_\050last_1_month\051.json"
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-20 20:17:15.922486",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Opportunity",
+ "dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Opportunity\",\"creation\",\"Timespan\",\"last month\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Won Opportunity (Last 1 Month)",
+ "modified": "2020-07-22 16:15:53.088837",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Won Opportunity (Last 1 Month)",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py b/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py
index 6172a75..8fe16a2 100644
--- a/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py
+++ b/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py
@@ -17,7 +17,8 @@
{
"fieldname": "lead_owner",
"label": _("Lead Owner"),
- "fieldtype": "Data",
+ "fieldtype": "Link",
+ "options": "User",
"width": "130"
},
{
@@ -68,4 +69,4 @@
"fieldtype": "Float",
"width": "100"
}
- ]
\ No newline at end of file
+ ]
diff --git a/erpnext/education/api.py b/erpnext/education/api.py
index 1a19716..bf9f221 100644
--- a/erpnext/education/api.py
+++ b/erpnext/education/api.py
@@ -104,6 +104,7 @@
student_attendance.date = date
student_attendance.status = status
student_attendance.save()
+ student_attendance.submit()
@frappe.whitelist()
@@ -151,7 +152,7 @@
:param fee_structure: Fee Structure.
"""
if fee_structure:
- fs = frappe.get_list("Fee Component", fields=["fees_category", "amount"] , filters={"parent": fee_structure}, order_by= "idx")
+ fs = frappe.get_list("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx")
return fs
@@ -363,9 +364,9 @@
select
name as program_enrollment, student_name, program, student_batch_name as student_batch,
student_category, academic_term, academic_year
- from
+ from
`tabProgram Enrollment`
- where
+ where
student = %s and academic_year = %s
order by creation''', (student, current_academic_year), as_dict=1)
diff --git a/erpnext/education/doctype/academic_term/academic_term_dashboard.py b/erpnext/education/doctype/academic_term/academic_term_dashboard.py
new file mode 100644
index 0000000..871e0f3
--- /dev/null
+++ b/erpnext/education/doctype/academic_term/academic_term_dashboard.py
@@ -0,0 +1,25 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'academic_term',
+ 'transactions': [
+ {
+ 'label': _('Student'),
+ 'items': ['Student Applicant', 'Student Group', 'Student Log']
+ },
+ {
+ 'label': _('Fee'),
+ 'items': ['Fees', 'Fee Schedule', 'Fee Structure']
+ },
+ {
+ 'label': _('Program'),
+ 'items': ['Program Enrollment']
+ },
+ {
+ 'label': _('Assessment'),
+ 'items': ['Assessment Plan', 'Assessment Result']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/academic_year/academic_year.js b/erpnext/education/doctype/academic_year/academic_year.js
index 21caa63..0e86198 100644
--- a/erpnext/education/doctype/academic_year/academic_year.js
+++ b/erpnext/education/doctype/academic_year/academic_year.js
@@ -1,10 +1,2 @@
-frappe.ui.form.on("Academic Year", "refresh", function(frm) {
- if(!frm.doc.__islocal) {
- frm.add_custom_button(__("Student Group"), function() {
- frappe.route_options = {
- academic_year: frm.doc.name
- }
- frappe.set_route("List", "Student Group");
- });
- }
+frappe.ui.form.on("Academic Year", {
});
\ No newline at end of file
diff --git a/erpnext/education/doctype/academic_year/academic_year_dashboard.py b/erpnext/education/doctype/academic_year/academic_year_dashboard.py
new file mode 100644
index 0000000..f27f7d1
--- /dev/null
+++ b/erpnext/education/doctype/academic_year/academic_year_dashboard.py
@@ -0,0 +1,25 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'academic_year',
+ 'transactions': [
+ {
+ 'label': _('Student'),
+ 'items': ['Student Admission', 'Student Applicant', 'Student Group', 'Student Log']
+ },
+ {
+ 'label': _('Fee'),
+ 'items': ['Fees', 'Fee Schedule', 'Fee Structure']
+ },
+ {
+ 'label': _('Academic Term and Program'),
+ 'items': ['Academic Term', 'Program Enrollment']
+ },
+ {
+ 'label': _('Assessment'),
+ 'items': ['Assessment Plan', 'Assessment Result']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/article/article.js b/erpnext/education/doctype/article/article.js
index 4c9c6f0..edfec26 100644
--- a/erpnext/education/doctype/article/article.js
+++ b/erpnext/education/doctype/article/article.js
@@ -3,6 +3,54 @@
frappe.ui.form.on('Article', {
refresh: function(frm) {
+ if (!frm.doc.__islocal) {
+ frm.add_custom_button(__('Add to Topics'), function() {
+ frm.trigger('add_article_to_topics');
+ }, __('Action'));
+ }
+ },
+ add_article_to_topics: function(frm) {
+ get_topics_without_article(frm.doc.name).then(r => {
+ if (r.message.length) {
+ frappe.prompt([
+ {
+ fieldname: 'topics',
+ label: __('Topics'),
+ fieldtype: 'MultiSelectPills',
+ get_data: function() {
+ return r.message;
+ }
+ }
+ ],
+ function(data) {
+ frappe.call({
+ method: 'erpnext.education.doctype.topic.topic.add_content_to_topics',
+ args: {
+ 'content_type': 'Article',
+ 'content': frm.doc.name,
+ 'topics': data.topics,
+ },
+ callback: function(r) {
+ if (!r.exc) {
+ frm.reload_doc();
+ }
+ },
+ freeze: true,
+ freeze_message: __('...Adding Article to Topics')
+ });
+ }, __('Add Article to Topics'), __('Add'));
+ } else {
+ frappe.msgprint(__('This article is already added to the existing topics'));
+ }
+ });
}
});
+
+let get_topics_without_article = function(article) {
+ return frappe.call({
+ type: 'GET',
+ method: 'erpnext.education.doctype.article.article.get_topics_without_article',
+ args: {'article': article}
+ });
+};
\ No newline at end of file
diff --git a/erpnext/education/doctype/article/article.py b/erpnext/education/doctype/article/article.py
index 7dc850b..8ba367d 100644
--- a/erpnext/education/doctype/article/article.py
+++ b/erpnext/education/doctype/article/article.py
@@ -7,9 +7,15 @@
from frappe.model.document import Document
class Article(Document):
-
-
def get_article(self):
pass
-
+@frappe.whitelist()
+def get_topics_without_article(article):
+ data = []
+ for entry in frappe.db.get_all('Topic'):
+ topic = frappe.get_doc('Topic', entry.name)
+ topic_contents = [tc.content for tc in topic.topic_content]
+ if not topic_contents or article not in topic_contents:
+ data.append(topic.name)
+ return data
\ No newline at end of file
diff --git a/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py b/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py
new file mode 100644
index 0000000..2649d4b
--- /dev/null
+++ b/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'assessment_group',
+ 'transactions': [
+ {
+ 'label': _('Assessment'),
+ 'items': ['Assessment Plan', 'Assessment Result']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/assessment_plan/assessment_plan.js b/erpnext/education/doctype/assessment_plan/assessment_plan.js
index 0cb642b..c4c5614 100644
--- a/erpnext/education/doctype/assessment_plan/assessment_plan.js
+++ b/erpnext/education/doctype/assessment_plan/assessment_plan.js
@@ -2,9 +2,9 @@
// For license information, please see license.txt
-frappe.ui.form.on("Assessment Plan", {
+frappe.ui.form.on('Assessment Plan', {
onload: function(frm) {
- frm.set_query("assessment_group", function(doc, cdt, cdn) {
+ frm.set_query('assessment_group', function(doc, cdt, cdn) {
return{
filters: {
'is_group': 0
@@ -22,20 +22,20 @@
refresh: function(frm) {
if (frm.doc.docstatus == 1) {
- frm.add_custom_button(__("Assessment Result"), function() {
+ frm.add_custom_button(__('Assessment Result Tool'), function() {
frappe.route_options = {
assessment_plan: frm.doc.name,
student_group: frm.doc.student_group
}
- frappe.set_route("Form", "Assessment Result Tool");
- });
+ frappe.set_route('Form', 'Assessment Result Tool');
+ }, __('Tools'));
}
},
course: function(frm) {
if (frm.doc.course && frm.doc.maximum_assessment_score) {
frappe.call({
- method: "erpnext.education.api.get_assessment_criteria",
+ method: 'erpnext.education.api.get_assessment_criteria',
args: {
course: frm.doc.course
},
@@ -43,12 +43,12 @@
if (r.message) {
frm.doc.assessment_criteria = [];
$.each(r.message, function(i, d) {
- var row = frappe.model.add_child(frm.doc, "Assessment Plan Criteria", "assessment_criteria");
+ var row = frappe.model.add_child(frm.doc, 'Assessment Plan Criteria', 'assessment_criteria');
row.assessment_criteria = d.assessment_criteria;
row.maximum_score = d.weightage / 100 * frm.doc.maximum_assessment_score;
});
}
- refresh_field("assessment_criteria");
+ refresh_field('assessment_criteria');
}
});
@@ -56,6 +56,6 @@
},
maximum_assessment_score: function(frm) {
- frm.trigger("course");
+ frm.trigger('course');
}
});
\ No newline at end of file
diff --git a/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py b/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py
index c36dfb1..5e6c29d 100644
--- a/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py
+++ b/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py
@@ -6,12 +6,16 @@
def get_data():
return {
'fieldname': 'assessment_plan',
- 'non_standard_fieldnames': {
- },
'transactions': [
{
'label': _('Assessment'),
'items': ['Assessment Result']
}
+ ],
+ 'reports': [
+ {
+ 'label': _('Report'),
+ 'items': ['Assessment Plan Status']
+ }
]
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/assessment_result/assessment_result.js b/erpnext/education/doctype/assessment_result/assessment_result.js
index 84865ca..63d1aee 100644
--- a/erpnext/education/doctype/assessment_result/assessment_result.js
+++ b/erpnext/education/doctype/assessment_result/assessment_result.js
@@ -1,9 +1,16 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on("Assessment Result", {
+frappe.ui.form.on('Assessment Result', {
+ refresh: function(frm) {
+ if (!frm.doc.__islocal) {
+ frm.trigger('setup_chart');
+ }
+ frm.set_df_property('details', 'read_only', 1);
+ },
+
onload: function(frm) {
- frm.set_query('assessment_plan', function(){
+ frm.set_query('assessment_plan', function() {
return {
filters: {
docstatus: 1
@@ -15,48 +22,83 @@
assessment_plan: function(frm) {
if (frm.doc.assessment_plan) {
frappe.call({
- method: "erpnext.education.api.get_assessment_details",
+ method: 'erpnext.education.api.get_assessment_details',
args: {
assessment_plan: frm.doc.assessment_plan
},
callback: function(r) {
if (r.message) {
- frm.doc.details = [];
+ frappe.model.clear_table(frm.doc, 'details');
$.each(r.message, function(i, d) {
- var row = frappe.model.add_child(frm.doc, "Assessment Result Detail", "details");
+ var row = frm.add_child('details');
row.assessment_criteria = d.assessment_criteria;
row.maximum_score = d.maximum_score;
});
+ frm.refresh_field('details');
}
- refresh_field("details");
}
});
}
+ },
+
+ setup_chart: function(frm) {
+ let labels = [];
+ let maximum_scores = [];
+ let scores = [];
+ $.each(frm.doc.details, function(_i, e) {
+ labels.push(e.assessment_criteria);
+ maximum_scores.push(e.maximum_score);
+ scores.push(e.score);
+ });
+
+ if (labels.length && maximum_scores.length && scores.length) {
+ frm.dashboard.chart_area.empty().removeClass('hidden');
+ new frappe.Chart('.form-graph', {
+ title: 'Assessment Results',
+ data: {
+ labels: labels,
+ datasets: [
+ {
+ name: 'Maximum Score',
+ chartType: 'bar',
+ values: maximum_scores,
+ },
+ {
+ name: 'Score Obtained',
+ chartType: 'bar',
+ values: scores,
+ }
+ ]
+ },
+ colors: ['#4CA746', '#98D85B'],
+ type: 'bar'
+ });
+ }
}
});
-frappe.ui.form.on("Assessment Result Detail", {
+frappe.ui.form.on('Assessment Result Detail', {
score: function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
- if(!d.maximum_score || !frm.doc.grading_scale) {
- d.score = "";
- frappe.throw(__("Please fill in all the details to generate Assessment Result."));
+ if (!d.maximum_score || !frm.doc.grading_scale) {
+ d.score = '';
+ frappe.throw(__('Please fill in all the details to generate Assessment Result.'));
}
if (d.score > d.maximum_score) {
- frappe.throw(__("Score cannot be greater than Maximum Score"));
+ frappe.throw(__('Score cannot be greater than Maximum Score'));
}
else {
frappe.call({
- method: "erpnext.education.api.get_grade",
+ method: 'erpnext.education.api.get_grade',
args: {
grading_scale: frm.doc.grading_scale,
percentage: ((d.score/d.maximum_score) * 100)
},
callback: function(r) {
if (r.message) {
- frappe.model.set_value(cdt, cdn, "grade", r.message);
+ frappe.model.set_value(cdt, cdn, 'grade', r.message);
}
}
});
diff --git a/erpnext/education/doctype/assessment_result/assessment_result.json b/erpnext/education/doctype/assessment_result/assessment_result.json
index 212d47c..7a893aa 100644
--- a/erpnext/education/doctype/assessment_result/assessment_result.json
+++ b/erpnext/education/doctype/assessment_result/assessment_result.json
@@ -1,724 +1,182 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
+ "actions": [],
"allow_import": 1,
- "allow_rename": 0,
"autoname": "EDU-RES-.YYYY.-.#####",
- "beta": 0,
"creation": "2015-11-13 17:18:06.468332",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "assessment_plan",
+ "program",
+ "course",
+ "academic_year",
+ "academic_term",
+ "column_break_3",
+ "student",
+ "student_name",
+ "student_group",
+ "assessment_group",
+ "grading_scale",
+ "section_break_5",
+ "details",
+ "section_break_8",
+ "maximum_score",
+ "column_break_11",
+ "total_score",
+ "grade",
+ "section_break_13",
+ "comment",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "assessment_plan",
"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": "Assessment Plan",
- "length": 0,
- "no_copy": 0,
"options": "Assessment Plan",
- "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
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "assessment_plan.program",
"fieldname": "program",
"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": "Program",
- "length": 0,
- "no_copy": 0,
- "options": "Program",
- "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
+ "options": "Program"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "assessment_plan.course",
"fieldname": "course",
"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": "Course",
- "length": 0,
- "no_copy": 0,
- "options": "Course",
- "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
+ "options": "Course"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "assessment_plan.academic_year",
"fieldname": "academic_year",
"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": "Academic Year",
- "length": 0,
- "no_copy": 0,
- "options": "Academic Year",
- "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
+ "options": "Academic Year"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "assessment_plan.academic_term",
"fieldname": "academic_term",
"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": "Academic Term",
- "length": 0,
- "no_copy": 0,
- "options": "Academic Term",
- "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
+ "options": "Academic Term"
},
{
- "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
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "student",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
"in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Student",
- "length": 0,
- "no_copy": 0,
"options": "Student",
- "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
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "student.title",
"fieldname": "student_name",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Student Name",
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "assessment_plan.student_group",
"fieldname": "student_group",
"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": "Student Group",
- "length": 0,
- "no_copy": 0,
- "options": "Student Group",
- "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
+ "options": "Student Group"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "assessment_plan.assessment_group",
"fieldname": "assessment_group",
"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": "Assessment Group",
- "length": 0,
- "no_copy": 0,
- "options": "Assessment Group",
- "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
+ "options": "Assessment Group"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "assessment_plan.grading_scale",
"fieldname": "grading_scale",
"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": "Grading Scale",
- "length": 0,
- "no_copy": 0,
"options": "Grading Scale",
- "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
+ "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_5",
"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": "Result",
- "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
+ "label": "Result"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
"fieldname": "details",
"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": "Details",
- "length": 0,
- "no_copy": 0,
"options": "Assessment Result Detail",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_8",
- "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
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "assessment_plan.maximum_assessment_score",
"fieldname": "maximum_score",
"fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Maximum Score",
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "assessment_plan.maximum_assessment_score",
"fieldname": "column_break_11",
- "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
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "total_score",
"fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Total Score",
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "grade",
"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": "Grade",
- "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
+ "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_13",
"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": "Summary",
- "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
+ "label": "Summary"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "comment",
"fieldtype": "Small 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": "Comment",
- "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
+ "label": "Comment"
},
{
- "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": "Assessment Result",
- "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
+ "read_only": 1
}
],
- "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-08-30 02:10:36.813413",
+ "links": [],
+ "modified": "2020-08-03 11:47:54.119486",
"modified_by": "Administrator",
"module": "Education",
"name": "Assessment Result",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
@@ -728,28 +186,18 @@
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
- "set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
"restrict_to_domain": "Education",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
- "title_field": "student_name",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "title_field": "student_name"
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py b/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py
new file mode 100644
index 0000000..438379d
--- /dev/null
+++ b/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'reports': [
+ {
+ 'label': _('Reports'),
+ 'items': ['Final Assessment Grades', 'Course wise Assessment Report']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/assessment_result_detail/assessment_result_detail.json b/erpnext/education/doctype/assessment_result_detail/assessment_result_detail.json
index 85d943b..450f41c 100644
--- a/erpnext/education/doctype/assessment_result_detail/assessment_result_detail.json
+++ b/erpnext/education/doctype/assessment_result_detail/assessment_result_detail.json
@@ -1,194 +1,66 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2016-12-14 17:44:35.583123",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2016-12-14 17:44:35.583123",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "assessment_criteria",
+ "maximum_score",
+ "column_break_2",
+ "score",
+ "grade"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 4,
- "fieldname": "assessment_criteria",
- "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": "Assessment Criteria",
- "length": 0,
- "no_copy": 0,
- "options": "Assessment Criteria",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "columns": 4,
+ "fieldname": "assessment_criteria",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Assessment Criteria",
+ "options": "Assessment Criteria",
+ "read_only": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "maximum_score",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Maximum Score",
- "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,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "maximum_score",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Maximum Score",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 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,
- "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_break_2",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "score",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Score",
- "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
- },
+ "columns": 2,
+ "fieldname": "score",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Score",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "grade",
- "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": "Grade",
- "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,
- "unique": 0
+ "columns": 2,
+ "fieldname": "grade",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Grade",
+ "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": 1,
- "max_attachments": 0,
- "modified": "2017-11-10 19:11:14.362410",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Assessment Result Detail",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-07-31 13:27:17.699022",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Assessment Result Detail",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Education",
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/course/course.js b/erpnext/education/doctype/course/course.js
index 6932989..81e4a8c 100644
--- a/erpnext/education/doctype/course/course.js
+++ b/erpnext/education/doctype/course/course.js
@@ -1,41 +1,60 @@
-frappe.ui.form.on("Course", "refresh", function(frm) {
- if(!cur_frm.doc.__islocal) {
- frm.add_custom_button(__("Program"), function() {
- frappe.route_options = {
- "Program Course.course": frm.doc.name
- }
- frappe.set_route("List", "Program");
- });
+frappe.ui.form.on('Course', {
+ refresh: function(frm) {
+ if (!cur_frm.doc.__islocal) {
+ frm.add_custom_button(__('Add to Programs'), function() {
+ frm.trigger('add_course_to_programs')
+ }, __('Action'));
+ }
- frm.add_custom_button(__("Student Group"), function() {
- frappe.route_options = {
- course: frm.doc.name
+ frm.set_query('default_grading_scale', function(){
+ return {
+ filters: {
+ docstatus: 1
+ }
}
- frappe.set_route("List", "Student Group");
});
+ },
- frm.add_custom_button(__("Course Schedule"), function() {
- frappe.route_options = {
- course: frm.doc.name
+ add_course_to_programs: function(frm) {
+ get_programs_without_course(frm.doc.name).then(r => {
+ if (r.message.length) {
+ frappe.prompt([
+ {
+ fieldname: 'programs',
+ label: __('Programs'),
+ fieldtype: 'MultiSelectPills',
+ get_data: function() {
+ return r.message;
+ }
+ },
+ {
+ fieldtype: 'Check',
+ label: __('Is Mandatory'),
+ fieldname: 'mandatory',
+ }
+ ],
+ function(data) {
+ frappe.call({
+ method: 'erpnext.education.doctype.course.course.add_course_to_programs',
+ args: {
+ 'course': frm.doc.name,
+ 'programs': data.programs,
+ 'mandatory': data.mandatory
+ },
+ callback: function(r) {
+ if (!r.exc) {
+ frm.reload_doc();
+ }
+ },
+ freeze: true,
+ freeze_message: __('...Adding Course to Programs')
+ })
+ }, __('Add Course to Programs'), __('Add'));
+ } else {
+ frappe.msgprint(__('This course is already added to the existing programs'));
}
- frappe.set_route("List", "Course Schedule");
- });
-
- frm.add_custom_button(__("Assessment Plan"), function() {
- frappe.route_options = {
- course: frm.doc.name
- }
- frappe.set_route("List", "Assessment Plan");
});
}
-
- frm.set_query('default_grading_scale', function(){
- return {
- filters: {
- docstatus: 1
- }
- }
- });
});
frappe.ui.form.on('Course Topic', {
@@ -50,3 +69,11 @@
};
}
});
+
+let get_programs_without_course = function(course) {
+ return frappe.call({
+ type: 'GET',
+ method: 'erpnext.education.doctype.course.course.get_programs_without_course',
+ args: {'course': course}
+ });
+}
\ No newline at end of file
diff --git a/erpnext/education/doctype/course/course.py b/erpnext/education/doctype/course/course.py
index 0747a22..06efa54 100644
--- a/erpnext/education/doctype/course/course.py
+++ b/erpnext/education/doctype/course/course.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
+import json
from frappe.model.document import Document
from frappe import _
@@ -17,12 +18,39 @@
for criteria in self.assessment_criteria:
total_weightage += criteria.weightage or 0
if total_weightage != 100:
- frappe.throw(_("Total Weightage of all Assessment Criteria must be 100%"))
+ frappe.throw(_('Total Weightage of all Assessment Criteria must be 100%'))
def get_topics(self):
topic_data= []
for topic in self.topics:
- topic_doc = frappe.get_doc("Topic", topic.topic)
+ topic_doc = frappe.get_doc('Topic', topic.topic)
if topic_doc.topic_content:
topic_data.append(topic_doc)
- return topic_data
\ No newline at end of file
+ return topic_data
+
+
+@frappe.whitelist()
+def add_course_to_programs(course, programs, mandatory=False):
+ programs = json.loads(programs)
+ for entry in programs:
+ program = frappe.get_doc('Program', entry)
+ program.append('courses', {
+ 'course': course,
+ 'course_name': course,
+ 'mandatory': mandatory
+ })
+ program.flags.ignore_mandatory = True
+ program.save()
+ frappe.db.commit()
+ frappe.msgprint(_('Course {0} has been added to all the selected programs successfully.').format(frappe.bold(course)),
+ title=_('Programs updated'), indicator='green')
+
+@frappe.whitelist()
+def get_programs_without_course(course):
+ data = []
+ for entry in frappe.db.get_all('Program'):
+ program = frappe.get_doc('Program', entry.name)
+ courses = [c.course for c in program.courses]
+ if not courses or course not in courses:
+ data.append(program.name)
+ return data
\ No newline at end of file
diff --git a/erpnext/education/doctype/course/course_dashboard.py b/erpnext/education/doctype/course/course_dashboard.py
index 752af29..8a570bd 100644
--- a/erpnext/education/doctype/course/course_dashboard.py
+++ b/erpnext/education/doctype/course/course_dashboard.py
@@ -6,12 +6,10 @@
def get_data():
return {
'fieldname': 'course',
- 'non_standard_fieldnames': {
- },
'transactions': [
{
- 'label': _('Course'),
- 'items': ['Course Enrollment', 'Course Schedule']
+ 'label': _('Program and Course'),
+ 'items': ['Program', 'Course Enrollment', 'Course Schedule']
},
{
'label': _('Student'),
@@ -19,7 +17,7 @@
},
{
'label': _('Assessment'),
- 'items': ['Assessment Plan']
+ 'items': ['Assessment Plan', 'Assessment Result']
},
]
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py b/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py
new file mode 100644
index 0000000..b9dd457
--- /dev/null
+++ b/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'enrollment',
+ 'transactions': [
+ {
+ 'label': _('Activity'),
+ 'items': ['Course Activity', 'Quiz Activity']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/course_schedule/course_schedule.js b/erpnext/education/doctype/course_schedule/course_schedule.js
index 692c2a8..4275f6e 100644
--- a/erpnext/education/doctype/course_schedule/course_schedule.js
+++ b/erpnext/education/doctype/course_schedule/course_schedule.js
@@ -4,13 +4,13 @@
frappe.ui.form.on("Course Schedule", {
refresh: function(frm) {
if (!frm.doc.__islocal) {
- frm.add_custom_button(__("Attendance"), function() {
+ frm.add_custom_button(__("Mark Attendance"), function() {
frappe.route_options = {
based_on: "Course Schedule",
course_schedule: frm.doc.name
}
frappe.set_route("Form", "Student Attendance Tool");
- });
+ }).addClass("btn-primary");
}
}
});
\ No newline at end of file
diff --git a/erpnext/education/doctype/course_schedule/course_schedule.json b/erpnext/education/doctype/course_schedule/course_schedule.json
index 7346cab..8c6746b 100644
--- a/erpnext/education/doctype/course_schedule/course_schedule.json
+++ b/erpnext/education/doctype/course_schedule/course_schedule.json
@@ -1,520 +1,520 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2015-09-09 16:34:04.960369",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 1,
+ "allow_rename": 0,
+ "autoname": "naming_series:",
+ "beta": 0,
+ "creation": "2015-09-09 16:34:04.960369",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 0,
+ "engine": "InnoDB",
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student_group",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 1,
- "label": "Student Group",
- "length": 0,
- "no_copy": 0,
- "options": "Student Group",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "student_group",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 1,
+ "in_list_view": 0,
+ "in_standard_filter": 1,
+ "label": "Student Group",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Student Group",
+ "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
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "instructor",
- "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": 1,
- "label": "Instructor",
- "length": 0,
- "no_copy": 0,
- "options": "Instructor",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "instructor",
+ "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": 1,
+ "label": "Instructor",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Instructor",
+ "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
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "instructor.Instructor_name",
- "fieldname": "instructor_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Instructor Name",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_from": "instructor.Instructor_name",
+ "fieldname": "instructor_name",
+ "fieldtype": "Read Only",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 1,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Instructor Name",
+ "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
- },
+ },
{
- "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,
+ "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
- },
+ },
{
- "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": "Naming Series",
- "length": 0,
- "no_copy": 0,
- "options": "EDU-CSH-.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": 1,
- "translatable": 0,
+ "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": "Naming Series",
+ "length": 0,
+ "no_copy": 0,
+ "options": "EDU-CSH-.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": 1,
+ "translatable": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "course",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Course",
- "length": 0,
- "no_copy": 0,
- "options": "Course",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "course",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 1,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Course",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Course",
+ "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
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "color",
- "fieldtype": "Color",
- "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": "Color",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "color",
+ "fieldtype": "Color",
+ "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": "Color",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "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,
+ "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
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fieldname": "schedule_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": "Schedule 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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "Today",
+ "fieldname": "schedule_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": "Schedule 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": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "room",
- "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": "Room",
- "length": 0,
- "no_copy": 0,
- "options": "Room",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "room",
+ "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": "Room",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Room",
+ "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
- },
+ },
{
- "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,
+ "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
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "from_time",
- "fieldtype": "Time",
- "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": "From Time",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "from_time",
+ "fieldtype": "Time",
+ "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": "From Time",
+ "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
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "to_time",
- "fieldtype": "Time",
- "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": "To Time",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "to_time",
+ "fieldtype": "Time",
+ "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": "To Time",
+ "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
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "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": "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "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": "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
}
- ],
- "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,
- "menu_index": 0,
- "modified": "2018-08-21 14:44:51.827225",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Course Schedule",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "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,
+ "menu_index": 0,
+ "modified": "2018-08-21 14:44:51.827225",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Course Schedule",
+ "name_case": "",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Academics User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "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": "Academics User",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 0,
- "sort_field": "schedule_date",
- "sort_order": "DESC",
- "title_field": "title",
- "track_changes": 0,
- "track_seen": 0,
+ ],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Education",
+ "show_name_in_global_search": 0,
+ "sort_field": "schedule_date",
+ "sort_order": "DESC",
+ "title_field": "title",
+ "track_changes": 0,
+ "track_seen": 0,
"track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py b/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py
new file mode 100644
index 0000000..0866cd6
--- /dev/null
+++ b/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'course_schedule',
+ 'transactions': [
+ {
+ 'label': _('Attendance'),
+ 'items': ['Student Attendance']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.js b/erpnext/education/doctype/fee_schedule/fee_schedule.js
index 1338331..75dd446 100644
--- a/erpnext/education/doctype/fee_schedule/fee_schedule.js
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule.js
@@ -3,13 +3,13 @@
frappe.ui.form.on('Fee Schedule', {
setup: function(frm) {
- frm.add_fetch("fee_structure", "receivable_account", "receivable_account");
- frm.add_fetch("fee_structure", "income_account", "income_account");
- frm.add_fetch("fee_structure", "cost_center", "cost_center");
+ frm.add_fetch('fee_structure', 'receivable_account', 'receivable_account');
+ frm.add_fetch('fee_structure', 'income_account', 'income_account');
+ frm.add_fetch('fee_structure', 'cost_center', 'cost_center');
},
onload: function(frm) {
- frm.set_query("receivable_account", function(doc) {
+ frm.set_query('receivable_account', function(doc) {
return {
filters: {
'account_type': 'Receivable',
@@ -18,7 +18,8 @@
}
};
});
- frm.set_query("income_account", function(doc) {
+
+ frm.set_query('income_account', function(doc) {
return {
filters: {
'account_type': 'Income Account',
@@ -27,57 +28,59 @@
}
};
});
- frm.set_query("student_group", "student_groups", function() {
+
+ frm.set_query('student_group', 'student_groups', function() {
return {
- "program": frm.doc.program,
- "academic_term": frm.doc.academic_term,
- "academic_year": frm.doc.academic_year,
- "disabled": 0
+ 'program': frm.doc.program,
+ 'academic_term': frm.doc.academic_term,
+ 'academic_year': frm.doc.academic_year,
+ 'disabled': 0
};
});
- frappe.realtime.on("fee_schedule_progress", function(data) {
+
+ frappe.realtime.on('fee_schedule_progress', function(data) {
if (data.reload && data.reload === 1) {
frm.reload_doc();
}
if (data.progress) {
- let progress_bar = $(cur_frm.dashboard.progress_area).find(".progress-bar");
+ let progress_bar = $(cur_frm.dashboard.progress_area).find('.progress-bar');
if (progress_bar) {
- $(progress_bar).removeClass("progress-bar-danger").addClass("progress-bar-success progress-bar-striped");
- $(progress_bar).css("width", data.progress+"%");
+ $(progress_bar).removeClass('progress-bar-danger').addClass('progress-bar-success progress-bar-striped');
+ $(progress_bar).css('width', data.progress+'%');
}
}
});
},
refresh: function(frm) {
- if(!frm.doc.__islocal && frm.doc.__onload && frm.doc.__onload.dashboard_info &&
- frm.doc.fee_creation_status=="Successful") {
+ if (!frm.doc.__islocal && frm.doc.__onload && frm.doc.__onload.dashboard_info &&
+ frm.doc.fee_creation_status === 'Successful') {
var info = frm.doc.__onload.dashboard_info;
frm.dashboard.add_indicator(__('Total Collected: {0}', [format_currency(info.total_paid,
info.currency)]), 'blue');
frm.dashboard.add_indicator(__('Total Outstanding: {0}', [format_currency(info.total_unpaid,
info.currency)]), info.total_unpaid ? 'orange' : 'green');
}
- if (frm.doc.fee_creation_status=="In Process") {
- frm.dashboard.add_progress("Fee Creation Status", "0");
+ if (frm.doc.fee_creation_status === 'In Process') {
+ frm.dashboard.add_progress('Fee Creation Status', '0');
}
- if (frm.doc.docstatus==1 && !frm.doc.fee_creation_status || frm.doc.fee_creation_status == "Failed") {
+ if (frm.doc.docstatus === 1 && !frm.doc.fee_creation_status || frm.doc.fee_creation_status === 'Failed') {
frm.add_custom_button(__('Create Fees'), function() {
frappe.call({
- method: "create_fees",
+ method: 'create_fees',
doc: frm.doc,
callback: function() {
frm.refresh();
}
});
- }, "fa fa-play", "btn-success");
+ }).addClass('btn-primary');;
}
- if (frm.doc.fee_creation_status == "Successful") {
- frm.add_custom_button(__("View Fees Records"), function() {
+ if (frm.doc.fee_creation_status === 'Successful') {
+ frm.add_custom_button(__('View Fees Records'), function() {
frappe.route_options = {
fee_schedule: frm.doc.name
};
- frappe.set_route("List", "Fees");
+ frappe.set_route('List', 'Fees');
});
}
@@ -86,35 +89,35 @@
fee_structure: function(frm) {
if (frm.doc.fee_structure) {
frappe.call({
- method: "erpnext.education.doctype.fee_schedule.fee_schedule.get_fee_structure",
+ method: 'erpnext.education.doctype.fee_schedule.fee_schedule.get_fee_structure',
args: {
- "target_doc": frm.doc.name,
- "source_name": frm.doc.fee_structure
+ 'target_doc': frm.doc.name,
+ 'source_name': frm.doc.fee_structure
},
callback: function(r) {
var doc = frappe.model.sync(r.message);
- frappe.set_route("Form", doc[0].doctype, doc[0].name);
+ frappe.set_route('Form', doc[0].doctype, doc[0].name);
}
});
}
}
});
-frappe.ui.form.on("Fee Schedule Student Group", {
+frappe.ui.form.on('Fee Schedule Student Group', {
student_group: function(frm, cdt, cdn) {
var row = locals[cdt][cdn];
if (row.student_group && frm.doc.academic_year) {
frappe.call({
- method: "erpnext.education.doctype.fee_schedule.fee_schedule.get_total_students",
+ method: 'erpnext.education.doctype.fee_schedule.fee_schedule.get_total_students',
args: {
- "student_group": row.student_group,
- "academic_year": frm.doc.academic_year,
- "academic_term": frm.doc.academic_term,
- "student_category": frm.doc.student_category
+ 'student_group': row.student_group,
+ 'academic_year': frm.doc.academic_year,
+ 'academic_term': frm.doc.academic_term,
+ 'student_category': frm.doc.student_category
},
callback: function(r) {
- if(!r.exc) {
- frappe.model.set_value(cdt, cdn, "total_students", r.message);
+ if (!r.exc) {
+ frappe.model.set_value(cdt, cdn, 'total_students', r.message);
}
}
});
diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.json b/erpnext/education/doctype/fee_schedule/fee_schedule.json
index 7918318..23b3212 100644
--- a/erpnext/education/doctype/fee_schedule/fee_schedule.json
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule.json
@@ -168,6 +168,7 @@
"fieldname": "grand_total_in_words",
"fieldtype": "Data",
"label": "In Words",
+ "length": 240,
"read_only": 1
},
{
@@ -272,7 +273,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-05-15 08:39:20.682837",
+ "modified": "2020-07-18 05:11:49.905457",
"modified_by": "Administrator",
"module": "Education",
"name": "Fee Schedule",
diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py b/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py
new file mode 100644
index 0000000..acfe400
--- /dev/null
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+
+def get_data():
+ return {
+ 'fieldname': 'fee_schedule',
+ 'transactions': [
+ {
+ 'items': ['Fees']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.js b/erpnext/education/doctype/fee_structure/fee_structure.js
index f09d2ef..b331c6d 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.js
+++ b/erpnext/education/doctype/fee_structure/fee_structure.js
@@ -3,21 +3,21 @@
frappe.ui.form.on('Fee Structure', {
setup: function(frm) {
- frm.add_fetch("company", "default_receivable_account", "receivable_account");
- frm.add_fetch("company", "default_income_account", "income_account");
- frm.add_fetch("company", "cost_center", "cost_center");
+ frm.add_fetch('company', 'default_receivable_account', 'receivable_account');
+ frm.add_fetch('company', 'default_income_account', 'income_account');
+ frm.add_fetch('company', 'cost_center', 'cost_center');
},
onload: function(frm) {
- frm.set_query("academic_term", function() {
+ frm.set_query('academic_term', function() {
return {
- "filters": {
- "academic_year": frm.doc.academic_year
+ 'filters': {
+ 'academic_year': frm.doc.academic_year
}
};
});
- frm.set_query("receivable_account", function(doc) {
+ frm.set_query('receivable_account', function(doc) {
return {
filters: {
'account_type': 'Receivable',
@@ -26,7 +26,7 @@
}
};
});
- frm.set_query("income_account", function(doc) {
+ frm.set_query('income_account', function(doc) {
return {
filters: {
'account_type': 'Income Account',
@@ -38,27 +38,27 @@
},
refresh: function(frm) {
- if(frm.doc.docstatus === 1) {
+ if (frm.doc.docstatus === 1) {
frm.add_custom_button(__('Create Fee Schedule'), function() {
frm.events.make_fee_schedule(frm);
- });
+ }).addClass('btn-primary');
}
},
make_fee_schedule: function(frm) {
frappe.model.open_mapped_doc({
- method: "erpnext.education.doctype.fee_structure.fee_structure.make_fee_schedule",
+ method: 'erpnext.education.doctype.fee_structure.fee_structure.make_fee_schedule',
frm: frm
});
}
});
-frappe.ui.form.on("Fee Component", {
+frappe.ui.form.on('Fee Component', {
amount: function(frm) {
var total_amount = 0;
- for(var i=0;i<frm.doc.components.length;i++) {
+ for (var i=0;i<frm.doc.components.length;i++) {
total_amount += frm.doc.components[i].amount;
}
- frm.set_value("total_amount", total_amount);
+ frm.set_value('total_amount', total_amount);
}
});
\ No newline at end of file
diff --git a/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py b/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py
new file mode 100644
index 0000000..73e314f
--- /dev/null
+++ b/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'fee_structure',
+ 'transactions': [
+ {
+ 'label': _('Fee'),
+ 'items': ['Fees', 'Fee Schedule']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/fees/fees.js b/erpnext/education/doctype/fees/fees.js
index 867866f..aaf42b4 100644
--- a/erpnext/education/doctype/fees/fees.js
+++ b/erpnext/education/doctype/fees/fees.js
@@ -162,6 +162,7 @@
$.each(r.message, function(i, d) {
var row = frappe.model.add_child(frm.doc, "Fee Component", "components");
row.fees_category = d.fees_category;
+ row.description = d.description;
row.amount = d.amount;
});
}
diff --git a/erpnext/education/doctype/fees/fees.json b/erpnext/education/doctype/fees/fees.json
index 676ff30..99f9f4f 100644
--- a/erpnext/education/doctype/fees/fees.json
+++ b/erpnext/education/doctype/fees/fees.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2015-09-22 16:57:22.143710",
@@ -253,6 +254,7 @@
"fieldname": "grand_total_in_words",
"fieldtype": "Data",
"label": "In Words",
+ "length": 240,
"read_only": 1
},
{
@@ -336,7 +338,8 @@
}
],
"is_submittable": 1,
- "modified": "2019-05-25 22:58:20.026368",
+ "links": [],
+ "modified": "2020-07-18 05:00:00.621010",
"modified_by": "Administrator",
"module": "Education",
"name": "Fees",
diff --git a/erpnext/education/doctype/grading_scale/grading_scale_dashboard.py b/erpnext/education/doctype/grading_scale/grading_scale_dashboard.py
new file mode 100644
index 0000000..2a3f13b
--- /dev/null
+++ b/erpnext/education/doctype/grading_scale/grading_scale_dashboard.py
@@ -0,0 +1,20 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'grading_scale',
+ 'non_standard_fieldnames': {
+ 'Course': 'default_grading_scale'
+ },
+ 'transactions': [
+ {
+ 'label': _('Course'),
+ 'items': ['Course']
+ },
+ {
+ 'label': _('Assessment'),
+ 'items': ['Assessment Plan', 'Assessment Result']
+ }
+ ]
+ }
diff --git a/erpnext/education/doctype/instructor/instructor.js b/erpnext/education/doctype/instructor/instructor.js
index 69bd2cf..abb47ed 100644
--- a/erpnext/education/doctype/instructor/instructor.js
+++ b/erpnext/education/doctype/instructor/instructor.js
@@ -3,8 +3,8 @@
frappe.ui.form.on("Instructor", {
employee: function(frm) {
- if(!frm.doc.employee) return;
- frappe.db.get_value('Employee', {name: frm.doc.employee}, 'company', (d) => {
+ if (!frm.doc.employee) return;
+ frappe.db.get_value("Employee", {name: frm.doc.employee}, "company", (d) => {
frm.set_query("department", function() {
return {
"filters": {
@@ -22,30 +22,16 @@
});
},
refresh: function(frm) {
- if(!frm.doc.__islocal) {
- frm.add_custom_button(__("Student Group"), function() {
- frappe.route_options = {
- instructor: frm.doc.name
- }
- frappe.set_route("List", "Student Group");
- });
- frm.add_custom_button(__("Course Schedule"), function() {
- frappe.route_options = {
- instructor: frm.doc.name
- }
- frappe.set_route("List", "Course Schedule");
- });
+ if (!frm.doc.__islocal) {
frm.add_custom_button(__("As Examiner"), function() {
- frappe.route_options = {
+ frappe.new_doc("Assessment Plan", {
examiner: frm.doc.name
- }
- frappe.set_route("List", "Assessment Plan");
+ });
}, __("Assessment Plan"));
frm.add_custom_button(__("As Supervisor"), function() {
- frappe.route_options = {
+ frappe.new_doc("Assessment Plan", {
supervisor: frm.doc.name
- }
- frappe.set_route("List", "Assessment Plan");
+ });
}, __("Assessment Plan"));
}
frm.set_query("employee", function(doc) {
diff --git a/erpnext/education/doctype/instructor/instructor.py b/erpnext/education/doctype/instructor/instructor.py
index 28df2fc..b1bfcbb 100644
--- a/erpnext/education/doctype/instructor/instructor.py
+++ b/erpnext/education/doctype/instructor/instructor.py
@@ -30,4 +30,14 @@
if self.employee and frappe.db.get_value("Instructor", {'employee': self.employee, 'name': ['!=', self.name]}, 'name'):
frappe.throw(_("Employee ID is linked with another instructor"))
-
+def get_timeline_data(doctype, name):
+ """Return timeline for course schedule"""
+ return dict(frappe.db.sql(
+ """
+ SELECT unix_timestamp(`schedule_date`), count(*)
+ FROM `tabCourse Schedule`
+ WHERE
+ instructor=%s and
+ `schedule_date` > date_sub(curdate(), interval 1 year)
+ GROUP BY schedule_date
+ """, name))
diff --git a/erpnext/education/doctype/instructor/instructor_dashboard.py b/erpnext/education/doctype/instructor/instructor_dashboard.py
new file mode 100644
index 0000000..a404fc5
--- /dev/null
+++ b/erpnext/education/doctype/instructor/instructor_dashboard.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'heatmap': True,
+ 'heatmap_message': _('This is based on the course schedules of this Instructor'),
+ 'fieldname': 'instructor',
+ 'non_standard_fieldnames': {
+ 'Assessment Plan': 'supervisor'
+ },
+ 'transactions': [
+ {
+ 'label': _('Course and Assessment'),
+ 'items': ['Course Schedule', 'Assessment Plan']
+ },
+ {
+ 'label': _('Students'),
+ 'items': ['Student Group']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/program/program_dashboard.py b/erpnext/education/doctype/program/program_dashboard.py
index cb8f742..c5d2494 100644
--- a/erpnext/education/doctype/program/program_dashboard.py
+++ b/erpnext/education/doctype/program/program_dashboard.py
@@ -10,11 +10,15 @@
},
{
'label': _('Student Activity'),
- 'items': ['Student Group' ]
+ 'items': ['Student Group', 'Student Log']
},
{
'label': _('Fee'),
- 'items': ['Fees','Fee Structure']
+ 'items': ['Fees','Fee Structure', 'Fee Schedule']
+ },
+ {
+ 'label': _('Assessment'),
+ 'items': ['Assessment Plan', 'Assessment Result']
}
]
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py
index 7536172..3e27670 100644
--- a/erpnext/education/doctype/program_enrollment/program_enrollment.py
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py
@@ -97,6 +97,7 @@
return quiz_progress
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_program_courses(doctype, txt, searchfield, start, page_len, filters):
if filters.get('program'):
return frappe.db.sql("""select course, course_name from `tabProgram Course`
@@ -115,6 +116,7 @@
})
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_students(doctype, txt, searchfield, start, page_len, filters):
if not filters.get("academic_term"):
filters["academic_term"] = frappe.defaults.get_defaults().academic_term
diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py b/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py
new file mode 100644
index 0000000..18d307c
--- /dev/null
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py
@@ -0,0 +1,19 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'program_enrollment',
+ 'transactions': [
+ {
+ 'label': _('Course and Fee'),
+ 'items': ['Course Enrollment', 'Fees']
+ }
+ ],
+ 'reports': [
+ {
+ 'label': _('Report'),
+ 'items': ['Student and Guardian Contact Details']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/quiz/quiz.js b/erpnext/education/doctype/quiz/quiz.js
index 7b87088..01bcf73 100644
--- a/erpnext/education/doctype/quiz/quiz.js
+++ b/erpnext/education/doctype/quiz/quiz.js
@@ -3,11 +3,17 @@
frappe.ui.form.on('Quiz', {
refresh: function(frm) {
-
+ if (!frm.doc.__islocal) {
+ frm.add_custom_button(__('Add to Topics'), function() {
+ frm.trigger('add_quiz_to_topics');
+ }, __('Action'));
+ }
},
+
validate: function(frm){
frm.events.check_duplicate_question(frm.doc.question);
},
+
check_duplicate_question: function(questions_data){
var questions = [];
questions_data.forEach(function(q){
@@ -15,7 +21,51 @@
});
var questions_set = new Set(questions);
if (questions.length != questions_set.size) {
- frappe.throw(__("The question cannot be duplicate"));
+ frappe.throw(__('The question cannot be duplicate'));
}
+ },
+
+ add_quiz_to_topics: function(frm) {
+ get_topics_without_quiz(frm.doc.name).then(r => {
+ if (r.message.length) {
+ frappe.prompt([
+ {
+ fieldname: 'topics',
+ label: __('Topics'),
+ fieldtype: 'MultiSelectPills',
+ get_data: function() {
+ return r.message;
+ }
+ }
+ ],
+ function(data) {
+ frappe.call({
+ method: 'erpnext.education.doctype.topic.topic.add_content_to_topics',
+ args: {
+ 'content_type': 'Quiz',
+ 'content': frm.doc.name,
+ 'topics': data.topics,
+ },
+ callback: function(r) {
+ if (!r.exc) {
+ frm.reload_doc();
+ }
+ },
+ freeze: true,
+ freeze_message: __('...Adding Quiz to Topics')
+ });
+ }, __('Add Quiz to Topics'), __('Add'));
+ } else {
+ frappe.msgprint(__('This quiz is already added to the existing topics'));
+ }
+ });
}
-});
\ No newline at end of file
+});
+
+let get_topics_without_quiz = function(quiz) {
+ return frappe.call({
+ type: 'GET',
+ method: 'erpnext.education.doctype.quiz.quiz.get_topics_without_quiz',
+ args: {'quiz': quiz}
+ });
+};
\ No newline at end of file
diff --git a/erpnext/education/doctype/quiz/quiz.py b/erpnext/education/doctype/quiz/quiz.py
index ae1cb6c..a774b88 100644
--- a/erpnext/education/doctype/quiz/quiz.py
+++ b/erpnext/education/doctype/quiz/quiz.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
+import json
from frappe import _
from frappe.model.document import Document
@@ -59,3 +60,12 @@
except TypeError:
frappe.throw(_("Compare List function takes on list arguments"))
+@frappe.whitelist()
+def get_topics_without_quiz(quiz):
+ data = []
+ for entry in frappe.db.get_all('Topic'):
+ topic = frappe.get_doc('Topic', entry.name)
+ topic_contents = [tc.content for tc in topic.topic_content]
+ if not topic_contents or quiz not in topic_contents:
+ data.append(topic.name)
+ return data
\ No newline at end of file
diff --git a/erpnext/education/doctype/room/room.js b/erpnext/education/doctype/room/room.js
index 032db98..20cee6b 100644
--- a/erpnext/education/doctype/room/room.js
+++ b/erpnext/education/doctype/room/room.js
@@ -1,10 +1,2 @@
-frappe.ui.form.on("Room", "refresh", function(frm) {
- if(!cur_frm.doc.__islocal) {
- frm.add_custom_button(__("Course Schedule"), function() {
- frappe.route_options = {
- room: frm.doc.name
- }
- frappe.set_route("List", "Course Schedule");
- });
- }
+frappe.ui.form.on("Room", {
});
\ No newline at end of file
diff --git a/erpnext/education/doctype/room/room_dashboard.py b/erpnext/education/doctype/room/room_dashboard.py
new file mode 100644
index 0000000..99aac33
--- /dev/null
+++ b/erpnext/education/doctype/room/room_dashboard.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'room',
+ 'transactions': [
+ {
+ 'label': _('Course'),
+ 'items': ['Course Schedule']
+ },
+ {
+ 'label': _('Assessment'),
+ 'items': ['Assessment Plan']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index 6b545d9..e0d7514 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -25,7 +25,7 @@
for sibling in self.siblings:
if sibling.date_of_birth and getdate(sibling.date_of_birth) > getdate():
frappe.throw(_("Row {0}:Sibling Date of Birth cannot be greater than today.").format(sibling.idx))
-
+
if self.date_of_birth and getdate(self.date_of_birth) >= getdate(today()):
frappe.throw(_("Date of Birth cannot be greater than today."))
@@ -157,5 +157,5 @@
from `tabStudent Attendance` where
student=%s
and `date` > date_sub(curdate(), interval 1 year)
- and status = 'Present'
+ and docstatus = 1 and status = 'Present'
group by date''', name))
diff --git a/erpnext/education/doctype/student_attendance/student_attendance.json b/erpnext/education/doctype/student_attendance/student_attendance.json
index 23e10e6..55384b9 100644
--- a/erpnext/education/doctype/student_attendance/student_attendance.json
+++ b/erpnext/education/doctype/student_attendance/student_attendance.json
@@ -1,287 +1,125 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2015-11-05 15:20:23.045996",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_import": 1,
+ "autoname": "naming_series:",
+ "creation": "2015-11-05 15:20:23.045996",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "naming_series",
+ "student",
+ "student_name",
+ "course_schedule",
+ "student_group",
+ "column_break_3",
+ "date",
+ "status",
+ "leave_application",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 1,
- "label": "Student",
- "length": 0,
- "no_copy": 0,
- "options": "Student",
- "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": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "student",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "in_standard_filter": 1,
+ "label": "Student",
+ "options": "Student",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "course_schedule",
- "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": "Course Schedule",
- "length": 0,
- "no_copy": 0,
- "options": "Course Schedule",
- "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": "course_schedule",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Course Schedule",
+ "options": "Course Schedule"
+ },
{
- "allow_bulk_edit": 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": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "label": "Date",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "student.title",
- "fieldname": "student_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Student Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "student_name",
+ "fieldtype": "Read Only",
+ "in_global_search": 1,
+ "label": "Student Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student_group",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 1,
- "label": "Student Group",
- "length": 0,
- "no_copy": 0,
- "options": "Student Group",
- "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": "student_group",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "in_standard_filter": 1,
+ "label": "Student Group",
+ "options": "Student Group"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Present",
- "fieldname": "status",
- "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": 1,
- "label": "Status",
- "length": 0,
- "no_copy": 0,
- "options": "Present\nAbsent",
- "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
+ "default": "Present",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Status",
+ "options": "Present\nAbsent",
+ "reqd": 1
+ },
+ {
+ "fieldname": "leave_application",
+ "fieldtype": "Link",
+ "label": "Leave Application",
+ "options": "Student Leave Application",
+ "read_only": 1
+ },
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "options": "EDU-ATT-.YYYY.-"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Student Attendance",
+ "print_hide": 1,
+ "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-07-27 10:48:22.301531",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Attendance",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-07-08 13:55:42.580181",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Attendance",
+ "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": "Academics User",
- "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": "Academics User",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "student_name",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "restrict_to_domain": "Education",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "student_name"
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_attendance/student_attendance.py b/erpnext/education/doctype/student_attendance/student_attendance.py
index 06ac4fb..c1b6850 100644
--- a/erpnext/education/doctype/student_attendance/student_attendance.py
+++ b/erpnext/education/doctype/student_attendance/student_attendance.py
@@ -6,52 +6,63 @@
import frappe
from frappe.model.document import Document
from frappe import _
-from frappe.utils import cstr
+from frappe.utils import get_link_to_form
from erpnext.education.api import get_student_group_students
class StudentAttendance(Document):
def validate(self):
- self.validate_date()
self.validate_mandatory()
- self.validate_course_schedule()
+ self.set_date()
+ self.set_student_group()
self.validate_student()
self.validate_duplication()
-
- def validate_date(self):
+
+ def set_date(self):
if self.course_schedule:
- self.date = frappe.db.get_value("Course Schedule", self.course_schedule, "schedule_date")
-
+ self.date = frappe.db.get_value('Course Schedule', self.course_schedule, 'schedule_date')
+
def validate_mandatory(self):
if not (self.student_group or self.course_schedule):
- frappe.throw(_("""Student Group or Course Schedule is mandatory"""))
-
- def validate_course_schedule(self):
+ frappe.throw(_('{0} or {1} is mandatory').format(frappe.bold('Student Group'),
+ frappe.bold('Course Schedule')), title=_('Mandatory Fields'))
+
+ def set_student_group(self):
if self.course_schedule:
- self.student_group = frappe.db.get_value("Course Schedule", self.course_schedule, "student_group")
-
+ self.student_group = frappe.db.get_value('Course Schedule', self.course_schedule, 'student_group')
+
def validate_student(self):
if self.course_schedule:
- student_group = frappe.db.get_value("Course Schedule", self.course_schedule, "student_group")
+ student_group = frappe.db.get_value('Course Schedule', self.course_schedule, 'student_group')
else:
student_group = self.student_group
student_group_students = [d.student for d in get_student_group_students(student_group)]
if student_group and self.student not in student_group_students:
- frappe.throw(_('''Student {0}: {1} does not belong to Student Group {2}'''.format(self.student, self.student_name, student_group)))
+ student_group_doc = get_link_to_form('Student Group', student_group)
+ frappe.throw(_('Student {0}: {1} does not belong to Student Group {2}').format(
+ frappe.bold(self.student), self.student_name, frappe.bold(student_group_doc)))
def validate_duplication(self):
"""Check if the Attendance Record is Unique"""
- attendance_records=None
+ attendance_record = None
if self.course_schedule:
- attendance_records= frappe.db.sql("""select name from `tabStudent Attendance` where \
- student= %s and ifnull(course_schedule, '')= %s and name != %s""",
- (self.student, cstr(self.course_schedule), self.name))
+ attendance_record = frappe.db.exists('Student Attendance', {
+ 'student': self.student,
+ 'course_schedule': self.course_schedule,
+ 'docstatus': ('!=', 2),
+ 'name': ('!=', self.name)
+ })
else:
- attendance_records= frappe.db.sql("""select name from `tabStudent Attendance` where \
- student= %s and student_group= %s and date= %s and name != %s and \
- (course_schedule is Null or course_schedule='')""",
- (self.student, self.student_group, self.date, self.name))
-
- if attendance_records:
- frappe.throw(_("Attendance Record {0} exists against Student {1}")
- .format(attendance_records[0][0], self.student))
+ attendance_record = frappe.db.exists('Student Attendance', {
+ 'student': self.student,
+ 'student_group': self.student_group,
+ 'date': self.date,
+ 'docstatus': ('!=', 2),
+ 'name': ('!=', self.name),
+ 'course_schedule': ''
+ })
+
+ if attendance_record:
+ record = get_link_to_form('Attendance Record', attendance_record)
+ frappe.throw(_('Student Attendance record {0} already exists against the Student {1}')
+ .format(record, frappe.bold(self.student)), title=_('Duplicate Entry'))
diff --git a/erpnext/education/doctype/student_attendance/student_attendance_dashboard.py b/erpnext/education/doctype/student_attendance/student_attendance_dashboard.py
new file mode 100644
index 0000000..9c41b8f
--- /dev/null
+++ b/erpnext/education/doctype/student_attendance/student_attendance_dashboard.py
@@ -0,0 +1,12 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'reports': [
+ {
+ 'label': _('Reports'),
+ 'items': ['Student Monthly Attendance Sheet', 'Student Batch-Wise Attendance']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_category/student_category_dashboard.py b/erpnext/education/doctype/student_category/student_category_dashboard.py
new file mode 100644
index 0000000..f31c34b
--- /dev/null
+++ b/erpnext/education/doctype/student_category/student_category_dashboard.py
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'student_category',
+ 'transactions': [
+ {
+ 'label': _('Fee'),
+ 'items': ['Fee Structure', 'Fee Schedule', 'Fees']
+ }
+ ]
+ }
diff --git a/erpnext/education/doctype/student_group/student_group.js b/erpnext/education/doctype/student_group/student_group.js
index 1372440..51e3b74 100644
--- a/erpnext/education/doctype/student_group/student_group.js
+++ b/erpnext/education/doctype/student_group/student_group.js
@@ -1,18 +1,18 @@
-cur_frm.add_fetch("student", "title", "student_name");
+cur_frm.add_fetch('student', 'title', 'student_name');
-frappe.ui.form.on("Student Group", {
+frappe.ui.form.on('Student Group', {
onload: function(frm) {
- frm.set_query("academic_term", function() {
+ frm.set_query('academic_term', function() {
return {
- "filters": {
- "academic_year": (frm.doc.academic_year)
+ filters: {
+ 'academic_year': (frm.doc.academic_year)
}
};
});
if (!frm.__islocal) {
- frm.set_query("student", "students", function() {
+ frm.set_query('student', 'students', function() {
return{
- query: "erpnext.education.doctype.student_group.student_group.fetch_students",
+ query: 'erpnext.education.doctype.student_group.student_group.fetch_students',
filters: {
'academic_year': frm.doc.academic_year,
'group_based_on': frm.doc.group_based_on,
@@ -30,87 +30,86 @@
refresh: function(frm) {
if (!frm.doc.__islocal) {
- frm.add_custom_button(__("Attendance"), function() {
- frappe.route_options = {
- based_on: "Student Group",
- student_group: frm.doc.name
- }
- frappe.set_route("List", "Student Attendance Tool");
- });
- frm.add_custom_button(__("Course Schedule"), function() {
- frappe.route_options = {
- student_group: frm.doc.name
- }
- frappe.set_route("List", "Course Schedule");
- });
- frm.add_custom_button(__("Assessment Plan"), function() {
- frappe.route_options = {
- student_group: frm.doc.name
- }
- frappe.set_route("List", "Assessment Plan");
- });
- frm.add_custom_button(__("Update Email Group"), function() {
+
+ frm.add_custom_button(__('Add Guardians to Email Group'), function() {
frappe.call({
- method: "erpnext.education.api.update_email_group",
+ method: 'erpnext.education.api.update_email_group',
args: {
- "doctype": "Student Group",
- "name": frm.doc.name
+ 'doctype': 'Student Group',
+ 'name': frm.doc.name
}
});
- });
- frm.add_custom_button(__("Newsletter"), function() {
+ }, __('Actions'));
+
+ frm.add_custom_button(__('Student Attendance Tool'), function() {
frappe.route_options = {
- "Newsletter Email Group.email_group": frm.doc.name
+ based_on: 'Student Group',
+ student_group: frm.doc.name
}
- frappe.set_route("List", "Newsletter");
- });
+ frappe.set_route('Form', 'Student Attendance Tool', 'Student Attendance Tool');
+ }, __('Tools'));
+
+ frm.add_custom_button(__('Course Scheduling Tool'), function() {
+ frappe.route_options = {
+ student_group: frm.doc.name
+ }
+ frappe.set_route('Form', 'Course Scheduling Tool', 'Course Scheduling Tool');
+ }, __('Tools'));
+
+ frm.add_custom_button(__('Newsletter'), function() {
+ frappe.route_options = {
+ 'Newsletter Email Group.email_group': frm.doc.name
+ }
+ frappe.set_route('List', 'Newsletter');
+ }, __('View'));
+
}
},
-
+
group_based_on: function(frm) {
- if (frm.doc.group_based_on == "Batch") {
+ if (frm.doc.group_based_on == 'Batch') {
frm.doc.course = null;
frm.set_df_property('program', 'reqd', 1);
frm.set_df_property('course', 'reqd', 0);
}
- else if (frm.doc.group_based_on == "Course") {
+ else if (frm.doc.group_based_on == 'Course') {
frm.set_df_property('program', 'reqd', 0);
frm.set_df_property('course', 'reqd', 1);
}
- else if (frm.doc.group_based_on == "Activity") {
+ else if (frm.doc.group_based_on == 'Activity') {
frm.set_df_property('program', 'reqd', 0);
frm.set_df_property('course', 'reqd', 0);
}
},
get_students: function(frm) {
- if (frm.doc.group_based_on == "Batch" || frm.doc.group_based_on == "Course") {
+ if (frm.doc.group_based_on == 'Batch' || frm.doc.group_based_on == 'Course') {
var student_list = [];
var max_roll_no = 0;
- $.each(frm.doc.students, function(i,d) {
+ $.each(frm.doc.students, function(_i,d) {
student_list.push(d.student);
if (d.group_roll_number>max_roll_no) {
max_roll_no = d.group_roll_number;
}
});
- if(frm.doc.academic_year) {
+ if (frm.doc.academic_year) {
frappe.call({
- method: "erpnext.education.doctype.student_group.student_group.get_students",
+ method: 'erpnext.education.doctype.student_group.student_group.get_students',
args: {
- "academic_year": frm.doc.academic_year,
- "academic_term": frm.doc.academic_term,
- "group_based_on": frm.doc.group_based_on,
- "program": frm.doc.program,
- "batch" : frm.doc.batch,
- "student_category" : frm.doc.student_category,
- "course": frm.doc.course
+ 'academic_year': frm.doc.academic_year,
+ 'academic_term': frm.doc.academic_term,
+ 'group_based_on': frm.doc.group_based_on,
+ 'program': frm.doc.program,
+ 'batch' : frm.doc.batch,
+ 'student_category' : frm.doc.student_category,
+ 'course': frm.doc.course
},
callback: function(r) {
- if(r.message) {
+ if (r.message) {
$.each(r.message, function(i, d) {
if(!in_list(student_list, d.student)) {
- var s = frm.add_child("students");
+ var s = frm.add_child('students');
s.student = d.student;
s.student_name = d.student_name;
if (d.active === 0) {
@@ -119,16 +118,16 @@
s.group_roll_number = ++max_roll_no;
}
});
- refresh_field("students");
+ refresh_field('students');
frm.save();
} else {
- frappe.msgprint(__("Student Group is already updated."))
+ frappe.msgprint(__('Student Group is already updated.'))
}
}
})
}
} else {
- frappe.msgprint(__("Select students manually for the Activity based Group"));
+ frappe.msgprint(__('Select students manually for the Activity based Group'));
}
}
});
diff --git a/erpnext/education/doctype/student_group/student_group.py b/erpnext/education/doctype/student_group/student_group.py
index 8b61c89..0260b80 100644
--- a/erpnext/education/doctype/student_group/student_group.py
+++ b/erpnext/education/doctype/student_group/student_group.py
@@ -108,6 +108,7 @@
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def fetch_students(doctype, txt, searchfield, start, page_len, filters):
if filters.get("group_based_on") != "Activity":
enrolled_students = get_program_enrollment(filters.get('academic_year'), filters.get('academic_term'),
diff --git a/erpnext/education/doctype/student_group/student_group_dashboard.py b/erpnext/education/doctype/student_group/student_group_dashboard.py
new file mode 100644
index 0000000..ad7a6de
--- /dev/null
+++ b/erpnext/education/doctype/student_group/student_group_dashboard.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'student_group',
+ 'transactions': [
+ {
+ 'label': _('Assessment'),
+ 'items': ['Assessment Plan', 'Assessment Result']
+ },
+ {
+ 'label': _('Course'),
+ 'items': ['Course Schedule']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_leave_application/student_leave_application.json b/erpnext/education/doctype/student_leave_application/student_leave_application.json
index fe38b87..ad53976 100644
--- a/erpnext/education/doctype/student_leave_application/student_leave_application.json
+++ b/erpnext/education/doctype/student_leave_application/student_leave_application.json
@@ -1,375 +1,158 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "EDU-SLA-.YYYY.-.#####",
- "beta": 0,
- "creation": "2016-11-28 15:38:54.793854",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "EDU-SLA-.YYYY.-.#####",
+ "creation": "2016-11-28 15:38:54.793854",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "student",
+ "student_name",
+ "column_break_3",
+ "from_date",
+ "to_date",
+ "section_break_5",
+ "attendance_based_on",
+ "student_group",
+ "course_schedule",
+ "mark_as_present",
+ "column_break_11",
+ "reason",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Student",
- "length": 0,
- "no_copy": 0,
- "options": "Student",
- "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": "student",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "label": "Student",
+ "options": "Student",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "student.title",
- "fieldname": "student_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Student Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "student.title",
+ "fieldname": "student_name",
+ "fieldtype": "Read Only",
+ "in_global_search": 1,
+ "label": "Student Name",
+ "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_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,
- "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,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "from_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "From 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": "from_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "From Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "to_date",
- "fieldtype": "Date",
- "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": "To 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": "to_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "To Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Will show the student as Present in Student Monthly Attendance Report",
- "fieldname": "mark_as_present",
- "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": "Mark as Present",
- "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
- },
+ "default": "0",
+ "description": "Check this to mark the student as present in case the student is not attending the institute to participate or represent the institute in any event.\n\n",
+ "fieldname": "mark_as_present",
+ "fieldtype": "Check",
+ "label": "Mark as Present"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "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_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_5",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reason",
- "fieldtype": "Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Reason",
- "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": "reason",
+ "fieldtype": "Text",
+ "label": "Reason"
+ },
{
- "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": "Student Leave Application",
- "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": "Student Leave Application",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "allow_in_quick_entry": 1,
+ "default": "Student Group",
+ "fieldname": "attendance_based_on",
+ "fieldtype": "Select",
+ "label": "Attendance Based On",
+ "options": "Student Group\nCourse Schedule"
+ },
+ {
+ "allow_in_quick_entry": 1,
+ "depends_on": "eval:doc.attendance_based_on === \"Student Group\";",
+ "fieldname": "student_group",
+ "fieldtype": "Link",
+ "label": "Student Group",
+ "mandatory_depends_on": "eval:doc.attendance_based_on === \"Student Group\";",
+ "options": "Student Group"
+ },
+ {
+ "allow_in_quick_entry": 1,
+ "depends_on": "eval:doc.attendance_based_on === \"Course Schedule\";",
+ "fieldname": "course_schedule",
+ "fieldtype": "Link",
+ "label": "Course Schedule",
+ "mandatory_depends_on": "eval:doc.attendance_based_on === \"Course Schedule\";",
+ "options": "Course Schedule"
+ },
+ {
+ "fieldname": "column_break_11",
+ "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-08-21 16:15:50.807352",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Leave Application",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-07-08 13:22:38.329002",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Leave Application",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Instructor",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Instructor",
+ "submit": 1,
"write": 1
- },
+ },
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Academics User",
- "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": "Academics User",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "student_name",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "quick_entry": 1,
+ "restrict_to_domain": "Education",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "student_name"
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_leave_application/student_leave_application.py b/erpnext/education/doctype/student_leave_application/student_leave_application.py
index 410f0cc..c8841c9 100644
--- a/erpnext/education/doctype/student_leave_application/student_leave_application.py
+++ b/erpnext/education/doctype/student_leave_application/student_leave_application.py
@@ -5,17 +5,23 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import get_link_to_form
+from datetime import timedelta
+from frappe.utils import get_link_to_form, getdate
from frappe.model.document import Document
-from frappe import throw, _
class StudentLeaveApplication(Document):
def validate(self):
- self.validate_dates()
self.validate_duplicate()
+ self.validate_from_to_dates('from_date', 'to_date')
+
+ def on_submit(self):
+ self.update_attendance()
+
+ def on_cancel(self):
+ self.cancel_attendance()
def validate_duplicate(self):
- data = frappe.db.sql(""" select name from `tabStudent Leave Application`
+ data = frappe.db.sql("""select name from `tabStudent Leave Application`
where
((%(from_date)s > from_date and %(from_date)s < to_date) or
(%(to_date)s > from_date and %(to_date)s < to_date) or
@@ -29,10 +35,57 @@
}, as_dict=1)
if data:
- link = get_link_to_form("Student Leave Application", data[0].name)
- frappe.throw(_("Leave application {0} already exists against the student {1}")
- .format(link, self.student))
+ link = get_link_to_form('Student Leave Application', data[0].name)
+ frappe.throw(_('Leave application {0} already exists against the student {1}')
+ .format(link, frappe.bold(self.student)), title=_('Duplicate Entry'))
- def validate_dates(self):
- if self.to_date < self.from_date :
- throw(_("To Date cannot be less than From Date"))
\ No newline at end of file
+ def update_attendance(self):
+ for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
+ date = dt.strftime('%Y-%m-%d')
+
+ attendance = frappe.db.exists('Student Attendance', {
+ 'student': self.student,
+ 'date': date,
+ 'docstatus': ('!=', 2)
+ })
+
+ status = 'Present' if self.mark_as_present else 'Absent'
+ if attendance:
+ # update existing attendance record
+ values = dict()
+ values['status'] = status
+ values['leave_application'] = self.name
+ frappe.db.set_value('Student Attendance', attendance, values)
+ else:
+ # make a new attendance record
+ doc = frappe.new_doc('Student Attendance')
+ doc.student = self.student
+ doc.student_name = self.student_name
+ doc.date = date
+ doc.leave_application = self.name
+ doc.status = status
+ if self.attendance_based_on == 'Student Group':
+ doc.student_group = self.student_group
+ else:
+ doc.course_schedule = self.course_schedule
+ doc.insert(ignore_permissions=True, ignore_mandatory=True)
+ doc.submit()
+
+ def cancel_attendance(self):
+ if self.docstatus == 2:
+ attendance = frappe.db.sql("""
+ SELECT name
+ FROM `tabStudent Attendance`
+ WHERE
+ student = %s and
+ (date between %s and %s) and
+ docstatus < 2
+ """, (self.student, self.from_date, self.to_date), as_dict=1)
+
+ for name in attendance:
+ frappe.db.set_value('Student Attendance', name, 'docstatus', 2)
+
+
+def daterange(start_date, end_date):
+ for n in range(int ((end_date - start_date).days)+1):
+ yield start_date + timedelta(n)
diff --git a/erpnext/education/doctype/student_leave_application/student_leave_application_dashboard.py b/erpnext/education/doctype/student_leave_application/student_leave_application_dashboard.py
new file mode 100644
index 0000000..fdcc147
--- /dev/null
+++ b/erpnext/education/doctype/student_leave_application/student_leave_application_dashboard.py
@@ -0,0 +1,11 @@
+from __future__ import unicode_literals
+
+def get_data():
+ return {
+ 'fieldname': 'leave_application',
+ 'transactions': [
+ {
+ 'items': ['Student Attendance']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_leave_application/test_student_leave_application.py b/erpnext/education/doctype/student_leave_application/test_student_leave_application.py
index ddb30ac..e9b568a 100644
--- a/erpnext/education/doctype/student_leave_application/test_student_leave_application.py
+++ b/erpnext/education/doctype/student_leave_application/test_student_leave_application.py
@@ -5,8 +5,66 @@
import frappe
import unittest
-
-# test_records = frappe.get_test_records('Student Leave Application')
+from frappe.utils import getdate, add_days
+from erpnext.education.doctype.student_group.test_student_group import get_random_group
+from erpnext.education.doctype.student.test_student import create_student
class TestStudentLeaveApplication(unittest.TestCase):
- pass
+ def setUp(self):
+ frappe.db.sql("""delete from `tabStudent Leave Application`""")
+
+ def test_attendance_record_creation(self):
+ leave_application = create_leave_application()
+ attendance_record = frappe.db.exists('Student Attendance', {'leave_application': leave_application.name, 'status': 'Absent'})
+ self.assertTrue(attendance_record)
+
+ # mark as present
+ date = add_days(getdate(), -1)
+ leave_application = create_leave_application(date, date, 1)
+ attendance_record = frappe.db.exists('Student Attendance', {'leave_application': leave_application.name, 'status': 'Present'})
+ self.assertTrue(attendance_record)
+
+ def test_attendance_record_updated(self):
+ attendance = create_student_attendance()
+ create_leave_application()
+ self.assertEqual(frappe.db.get_value('Student Attendance', attendance.name, 'status'), 'Absent')
+
+ def test_attendance_record_cancellation(self):
+ leave_application = create_leave_application()
+ leave_application.cancel()
+ attendance_status = frappe.db.get_value('Student Attendance', {'leave_application': leave_application.name}, 'docstatus')
+ self.assertTrue(attendance_status, 2)
+
+
+def create_leave_application(from_date=None, to_date=None, mark_as_present=0):
+ student = get_student()
+
+ leave_application = frappe.get_doc({
+ 'doctype': 'Student Leave Application',
+ 'student': student.name,
+ 'attendance_based_on': 'Student Group',
+ 'student_group': get_random_group().name,
+ 'from_date': from_date if from_date else getdate(),
+ 'to_date': from_date if from_date else getdate(),
+ 'mark_as_present': mark_as_present
+ }).insert()
+ leave_application.submit()
+ return leave_application
+
+def create_student_attendance(date=None, status=None):
+ student = get_student()
+ attendance = frappe.get_doc({
+ 'doctype': 'Student Attendance',
+ 'student': student.name,
+ 'status': status if status else 'Present',
+ 'date': date if date else getdate(),
+ 'student_group': get_random_group().name
+ }).insert()
+ return attendance
+
+def get_student():
+ return create_student(dict(
+ email='test_student@gmail.com',
+ first_name='Test',
+ last_name='Student'
+ ))
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.py b/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.py
index c0a7359..17bc367 100644
--- a/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.py
+++ b/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.py
@@ -80,7 +80,7 @@
from_date, to_date = frappe.db.get_value("Academic Term", academic_term, ["term_start_date", "term_end_date"])
if from_date and to_date:
attendance = dict(frappe.db.sql('''select status, count(student) as no_of_days
- from `tabStudent Attendance` where student = %s
+ from `tabStudent Attendance` where student = %s and docstatus = 1
and date between %s and %s group by status''',
(student, from_date, to_date)))
if "Absent" not in attendance.keys():
diff --git a/erpnext/education/doctype/topic/topic.js b/erpnext/education/doctype/topic/topic.js
index 695c174..2002b0c 100644
--- a/erpnext/education/doctype/topic/topic.js
+++ b/erpnext/education/doctype/topic/topic.js
@@ -3,6 +3,53 @@
frappe.ui.form.on('Topic', {
refresh: function(frm) {
+ if (!cur_frm.doc.__islocal) {
+ frm.add_custom_button(__('Add to Courses'), function() {
+ frm.trigger('add_topic_to_courses');
+ }, __('Action'));
+ }
+ },
+ add_topic_to_courses: function(frm) {
+ get_courses_without_topic(frm.doc.name).then(r => {
+ if (r.message.length) {
+ frappe.prompt([
+ {
+ fieldname: 'courses',
+ label: __('Courses'),
+ fieldtype: 'MultiSelectPills',
+ get_data: function() {
+ return r.message;
+ }
+ }
+ ],
+ function(data) {
+ frappe.call({
+ method: 'erpnext.education.doctype.topic.topic.add_topic_to_courses',
+ args: {
+ 'topic': frm.doc.name,
+ 'courses': data.courses
+ },
+ callback: function(r) {
+ if (!r.exc) {
+ frm.reload_doc();
+ }
+ },
+ freeze: true,
+ freeze_message: __('...Adding Topic to Courses')
+ });
+ }, __('Add Topic to Courses'), __('Add'));
+ } else {
+ frappe.msgprint(__('This topic is already added to the existing courses'));
+ }
+ });
}
});
+
+let get_courses_without_topic = function(topic) {
+ return frappe.call({
+ type: 'GET',
+ method: 'erpnext.education.doctype.topic.topic.get_courses_without_topic',
+ args: {'topic': topic}
+ });
+};
\ No newline at end of file
diff --git a/erpnext/education/doctype/topic/topic.py b/erpnext/education/doctype/topic/topic.py
index 7e5da32..a5253e9 100644
--- a/erpnext/education/doctype/topic/topic.py
+++ b/erpnext/education/doctype/topic/topic.py
@@ -4,6 +4,8 @@
from __future__ import unicode_literals
import frappe
+import json
+from frappe import _
from frappe.model.document import Document
class Topic(Document):
@@ -14,4 +16,44 @@
except Exception as e:
frappe.log_error(frappe.get_traceback())
return None
- return content_data
\ No newline at end of file
+ return content_data
+
+@frappe.whitelist()
+def get_courses_without_topic(topic):
+ data = []
+ for entry in frappe.db.get_all('Course'):
+ course = frappe.get_doc('Course', entry.name)
+ topics = [t.topic for t in course.topics]
+ if not topics or topic not in topics:
+ data.append(course.name)
+ return data
+
+@frappe.whitelist()
+def add_topic_to_courses(topic, courses, mandatory=False):
+ courses = json.loads(courses)
+ for entry in courses:
+ course = frappe.get_doc('Course', entry)
+ course.append('topics', {
+ 'topic': topic,
+ 'topic_name': topic
+ })
+ course.flags.ignore_mandatory = True
+ course.save()
+ frappe.db.commit()
+ frappe.msgprint(_('Topic {0} has been added to all the selected courses successfully.').format(frappe.bold(topic)),
+ title=_('Courses updated'), indicator='green')
+
+@frappe.whitelist()
+def add_content_to_topics(content_type, content, topics):
+ topics = json.loads(topics)
+ for entry in topics:
+ topic = frappe.get_doc('Topic', entry)
+ topic.append('topic_content', {
+ 'content_type': content_type,
+ 'content': content,
+ })
+ topic.flags.ignore_mandatory = True
+ topic.save()
+ frappe.db.commit()
+ frappe.msgprint(_('{0} {1} has been added to all the selected topics successfully.').format(content_type, frappe.bold(content)),
+ title=_('Topics updated'), indicator='green')
\ No newline at end of file
diff --git a/erpnext/education/report/absent_student_report/absent_student_report.json b/erpnext/education/report/absent_student_report/absent_student_report.json
index 0d5eeba..92ad860 100644
--- a/erpnext/education/report/absent_student_report/absent_student_report.json
+++ b/erpnext/education/report/absent_student_report/absent_student_report.json
@@ -1,20 +1,21 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2013-05-13 14:04:03",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "modified": "2017-11-10 19:42:36.457449",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Absent Student Report",
- "owner": "Administrator",
- "ref_doctype": "Student Attendance",
- "report_name": "Absent Student Report",
- "report_type": "Script Report",
+ "add_total_row": 0,
+ "creation": "2013-05-13 14:04:03",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 3,
+ "is_standard": "Yes",
+ "modified": "2020-06-24 17:16:40.251116",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Absent Student Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Student Attendance",
+ "report_name": "Absent Student Report",
+ "report_type": "Script Report",
"roles": [
{
"role": "Academics User"
diff --git a/erpnext/education/report/absent_student_report/absent_student_report.py b/erpnext/education/report/absent_student_report/absent_student_report.py
index 8e6ce51..4e57cc6 100644
--- a/erpnext/education/report/absent_student_report/absent_student_report.py
+++ b/erpnext/education/report/absent_student_report/absent_student_report.py
@@ -11,7 +11,7 @@
if not filters.get("date"):
msgprint(_("Please select date"), raise_exception=1)
-
+
columns = get_columns(filters)
date = filters.get("date")
@@ -26,27 +26,27 @@
if not student.student in leave_applicants:
row = [student.student, student.student_name, student.student_group]
stud_details = frappe.db.get_value("Student", student.student, ['student_email_id', 'student_mobile_number'], as_dict=True)
-
+
if stud_details.student_email_id:
row+=[stud_details.student_email_id]
else:
row+= [""]
-
+
if stud_details.student_mobile_number:
row+=[stud_details.student_mobile_number]
else:
row+= [""]
if transportation_details.get(student.student):
row += transportation_details.get(student.student)
-
+
data.append(row)
-
+
return columns, data
def get_columns(filters):
- columns = [
- _("Student") + ":Link/Student:90",
- _("Student Name") + "::150",
+ columns = [
+ _("Student") + ":Link/Student:90",
+ _("Student Name") + "::150",
_("Student Group") + "::180",
_("Student Email Address") + "::180",
_("Student Mobile No.") + "::150",
@@ -56,15 +56,29 @@
return columns
def get_absent_students(date):
- absent_students = frappe.db.sql("""select student, student_name, student_group from `tabStudent Attendance`
- where status="Absent" and date = %s order by student_group, student_name""", date, as_dict=1)
+ absent_students = frappe.db.sql("""
+ SELECT student, student_name, student_group
+ FROM `tabStudent Attendance`
+ WHERE
+ status='Absent' and docstatus=1 and date = %s
+ ORDER BY
+ student_group, student_name""",
+ date, as_dict=1)
return absent_students
def get_leave_applications(date):
leave_applicants = []
- for student in frappe.db.sql("""select student from `tabStudent Leave Application`
- where docstatus = 1 and from_date <= %s and to_date >= %s""", (date, date)):
+ leave_applications = frappe.db.sql("""
+ SELECT student
+ FROM
+ `tabStudent Leave Application`
+ WHERE
+ docstatus = 1 and mark_as_present = 1 and
+ from_date <= %s and to_date >= %s
+ """, (date, date))
+ for student in leave_applications:
leave_applicants.append(student[0])
+
return leave_applicants
def get_transportation_details(date, student_list):
diff --git a/erpnext/education/report/assessment_plan_status/assessment_plan_status.json b/erpnext/education/report/assessment_plan_status/assessment_plan_status.json
index 3000bec..cbca648 100644
--- a/erpnext/education/report/assessment_plan_status/assessment_plan_status.json
+++ b/erpnext/education/report/assessment_plan_status/assessment_plan_status.json
@@ -1,20 +1,21 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2017-11-09 15:07:30.404428",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2017-11-28 18:35:44.903665",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Assessment Plan Status",
- "owner": "Administrator",
- "ref_doctype": "Assessment Plan",
- "report_name": "Assessment Plan Status",
- "report_type": "Script Report",
+ "add_total_row": 0,
+ "creation": "2017-11-09 15:07:30.404428",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-06-24 17:16:02.027410",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Assessment Plan Status",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Assessment Plan",
+ "report_name": "Assessment Plan Status",
+ "report_type": "Script Report",
"roles": [
{
"role": "Academics User"
diff --git a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.json b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.json
index 61976b4..416db9d 100644
--- a/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.json
+++ b/erpnext/education/report/course_wise_assessment_report/course_wise_assessment_report.json
@@ -1,24 +1,26 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2017-05-05 14:46:13.776133",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2018-02-08 15:11:24.904628",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Course wise Assessment Report",
- "owner": "Administrator",
- "ref_doctype": "Assessment Result",
- "report_name": "Course wise Assessment Report",
- "report_type": "Script Report",
+ "add_total_row": 0,
+ "creation": "2017-05-05 14:46:13.776133",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-06-24 17:15:15.477530",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Course wise Assessment Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "",
+ "ref_doctype": "Assessment Result",
+ "report_name": "Course wise Assessment Report",
+ "report_type": "Script Report",
"roles": [
{
"role": "Instructor"
- },
+ },
{
"role": "Education Manager"
}
diff --git a/erpnext/education/report/final_assessment_grades/final_assessment_grades.json b/erpnext/education/report/final_assessment_grades/final_assessment_grades.json
index 4d444b4..6a23494 100644
--- a/erpnext/education/report/final_assessment_grades/final_assessment_grades.json
+++ b/erpnext/education/report/final_assessment_grades/final_assessment_grades.json
@@ -1,24 +1,25 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2018-01-22 17:04:43.412054",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2019-02-08 15:11:35.339434",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Final Assessment Grades",
- "owner": "Administrator",
- "ref_doctype": "Assessment Result",
- "report_name": "Final Assessment Grades",
- "report_type": "Script Report",
+ "add_total_row": 0,
+ "creation": "2018-01-22 17:04:43.412054",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-06-24 17:13:35.373756",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Final Assessment Grades",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Assessment Result",
+ "report_name": "Final Assessment Grades",
+ "report_type": "Script Report",
"roles": [
{
"role": "Instructor"
- },
+ },
{
"role": "Education Manager"
}
diff --git a/erpnext/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.json b/erpnext/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.json
index fe7d158..fa9be65 100644
--- a/erpnext/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.json
+++ b/erpnext/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.json
@@ -1,24 +1,25 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2017-03-27 17:47:16.831433",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2017-11-10 19:42:30.300729",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student and Guardian Contact Details",
- "owner": "Administrator",
- "ref_doctype": "Program Enrollment",
- "report_name": "Student and Guardian Contact Details",
- "report_type": "Script Report",
+ "add_total_row": 0,
+ "creation": "2017-03-27 17:47:16.831433",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-06-24 17:16:50.639488",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student and Guardian Contact Details",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Program Enrollment",
+ "report_name": "Student and Guardian Contact Details",
+ "report_type": "Script Report",
"roles": [
{
"role": "Instructor"
- },
+ },
{
"role": "Academics User"
}
diff --git a/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.json b/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.json
index eb547b7..8baf8f9 100644
--- a/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.json
+++ b/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.json
@@ -1,20 +1,21 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2016-11-28 22:07:03.859124",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 2,
- "is_standard": "Yes",
- "modified": "2017-11-10 19:41:12.328346",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Batch-Wise Attendance",
- "owner": "Administrator",
- "ref_doctype": "Student Attendance",
- "report_name": "Student Batch-Wise Attendance",
- "report_type": "Script Report",
+ "add_total_row": 0,
+ "creation": "2016-11-28 22:07:03.859124",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 2,
+ "is_standard": "Yes",
+ "modified": "2020-06-24 17:16:59.823709",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Batch-Wise Attendance",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Student Attendance",
+ "report_name": "Student Batch-Wise Attendance",
+ "report_type": "Script Report",
"roles": [
{
"role": "Academics User"
diff --git a/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py b/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py
index 646e3f7..c65d233 100644
--- a/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py
+++ b/erpnext/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py
@@ -11,7 +11,7 @@
if not filters.get("date"):
msgprint(_("Please select date"), raise_exception=1)
-
+
columns = get_columns(filters)
active_student_group = get_active_student_group()
@@ -37,28 +37,28 @@
return columns, data
def get_columns(filters):
- columns = [
- _("Student Group") + ":Link/Student Group:250",
- _("Student Group Strength") + "::170",
- _("Present") + "::90",
+ columns = [
+ _("Student Group") + ":Link/Student Group:250",
+ _("Student Group Strength") + "::170",
+ _("Present") + "::90",
_("Absent") + "::90",
_("Not Marked") + "::90"
]
return columns
def get_active_student_group():
- active_student_groups = frappe.db.sql("""select name from `tabStudent Group` where group_based_on = "Batch"
+ active_student_groups = frappe.db.sql("""select name from `tabStudent Group` where group_based_on = "Batch"
and academic_year=%s order by name""", (frappe.defaults.get_defaults().academic_year), as_dict=1)
return active_student_groups
def get_student_group_strength(student_group):
- student_group_strength = frappe.db.sql("""select count(*) from `tabStudent Group Student`
+ student_group_strength = frappe.db.sql("""select count(*) from `tabStudent Group Student`
where parent = %s and active=1""", student_group)[0][0]
return student_group_strength
def get_student_attendance(student_group, date):
- student_attendance = frappe.db.sql("""select count(*) as count, status from `tabStudent Attendance` where \
- student_group= %s and date= %s and\
+ student_attendance = frappe.db.sql("""select count(*) as count, status from `tabStudent Attendance` where
+ student_group= %s and date= %s and docstatus = 1 and
(course_schedule is Null or course_schedule='') group by status""",
(student_group, date), as_dict=1)
return student_attendance
\ No newline at end of file
diff --git a/erpnext/education/report/student_fee_collection/student_fee_collection.json b/erpnext/education/report/student_fee_collection/student_fee_collection.json
index eb945cf..8deb865 100644
--- a/erpnext/education/report/student_fee_collection/student_fee_collection.json
+++ b/erpnext/education/report/student_fee_collection/student_fee_collection.json
@@ -1,21 +1,22 @@
{
- "add_total_row": 0,
- "creation": "2016-06-22 02:58:41.024538",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "modified": "2018-12-17 16:46:46.176620",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Fee Collection",
- "owner": "Administrator",
- "prepared_report": 0,
- "query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(grand_total) - sum(outstanding_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(grand_total) as \"Grand Total:Currency:150\"\nFROM\n `tabFees` \nGROUP BY\n student",
- "ref_doctype": "Fees",
- "report_name": "Student Fee Collection",
- "report_type": "Query Report",
+ "add_total_row": 0,
+ "creation": "2016-06-22 02:58:41.024538",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 3,
+ "is_standard": "Yes",
+ "modified": "2020-06-24 17:14:39.452551",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Fee Collection",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(grand_total) - sum(outstanding_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(grand_total) as \"Grand Total:Currency:150\"\nFROM\n `tabFees` \nGROUP BY\n student",
+ "ref_doctype": "Fees",
+ "report_name": "Student Fee Collection",
+ "report_type": "Query Report",
"roles": [
{
"role": "Academics User"
diff --git a/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.json b/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.json
index e10f190..1423d4f 100644
--- a/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.json
+++ b/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.json
@@ -1,20 +1,21 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2013-05-13 14:04:03",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "modified": "2017-11-10 19:42:43.376658",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Monthly Attendance Sheet",
- "owner": "Administrator",
- "ref_doctype": "Student Attendance",
- "report_name": "Student Monthly Attendance Sheet",
- "report_type": "Script Report",
+ "add_total_row": 0,
+ "creation": "2013-05-13 14:04:03",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 3,
+ "is_standard": "Yes",
+ "modified": "2020-06-24 17:16:13.307053",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Monthly Attendance Sheet",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Student Attendance",
+ "report_name": "Student Monthly Attendance Sheet",
+ "report_type": "Script Report",
"roles": [
{
"role": "Academics User"
diff --git a/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py b/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py
index 3f1d5b3..d820bfb 100644
--- a/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py
+++ b/erpnext/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py
@@ -57,8 +57,9 @@
return student_list
def get_attendance_list(from_date, to_date, student_group, students_list):
- attendance_list = frappe.db.sql('''select student, date, status
- from `tabStudent Attendance` where student_group = %s
+ attendance_list = frappe.db.sql('''select student, date, status
+ from `tabStudent Attendance` where student_group = %s
+ and docstatus = 1
and date between %s and %s
order by student, date''',
(student_group, from_date, to_date), as_dict=1)
@@ -75,10 +76,10 @@
def get_students_with_leave_application(from_date, to_date, students_list):
if not students_list: return
leave_applications = frappe.db.sql("""
- select student, from_date, to_date
- from `tabStudent Leave Application`
- where
- mark_as_present and docstatus = 1
+ select student, from_date, to_date
+ from `tabStudent Leave Application`
+ where
+ mark_as_present = 1 and docstatus = 1
and student in %(students)s
and (
from_date between %(from_date)s and %(to_date)s
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/erpnext_integrations/doctype/taxjar_settings/__init__.py
diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js
new file mode 100644
index 0000000..62d5709
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('TaxJar Settings', {
+ is_sandbox: (frm) => {
+ frm.toggle_reqd("api_key", !frm.doc.is_sandbox);
+ frm.toggle_reqd("sandbox_api_key", frm.doc.is_sandbox);
+ }
+});
diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json
new file mode 100644
index 0000000..c0d60f7
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json
@@ -0,0 +1,110 @@
+{
+ "actions": [],
+ "creation": "2017-06-15 08:21:24.624315",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "is_sandbox",
+ "taxjar_calculate_tax",
+ "taxjar_create_transactions",
+ "credentials",
+ "api_key",
+ "cb_keys",
+ "sandbox_api_key",
+ "configuration",
+ "tax_account_head",
+ "configuration_cb",
+ "shipping_account_head"
+ ],
+ "fields": [
+ {
+ "fieldname": "credentials",
+ "fieldtype": "Section Break",
+ "label": "Credentials"
+ },
+ {
+ "fieldname": "api_key",
+ "fieldtype": "Password",
+ "in_list_view": 1,
+ "label": "Live API Key",
+ "reqd": 1
+ },
+ {
+ "fieldname": "configuration",
+ "fieldtype": "Section Break",
+ "label": "Configuration"
+ },
+ {
+ "fieldname": "tax_account_head",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Tax Account Head",
+ "options": "Account",
+ "reqd": 1
+ },
+ {
+ "fieldname": "shipping_account_head",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Shipping Account Head",
+ "options": "Account",
+ "reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "is_sandbox",
+ "fieldtype": "Check",
+ "label": "Sandbox Mode"
+ },
+ {
+ "fieldname": "sandbox_api_key",
+ "fieldtype": "Password",
+ "label": "Sandbox API Key"
+ },
+ {
+ "fieldname": "configuration_cb",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "taxjar_create_transactions",
+ "fieldtype": "Check",
+ "label": "Create TaxJar Transaction"
+ },
+ {
+ "default": "0",
+ "fieldname": "taxjar_calculate_tax",
+ "fieldtype": "Check",
+ "label": "Enable Tax Calculation"
+ },
+ {
+ "fieldname": "cb_keys",
+ "fieldtype": "Column Break"
+ }
+ ],
+ "issingle": 1,
+ "links": [],
+ "modified": "2020-04-30 04:38:03.311089",
+ "modified_by": "Administrator",
+ "module": "ERPNext Integrations",
+ "name": "TaxJar Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py
new file mode 100644
index 0000000..7f5f0f0
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class TaxJarSettings(Document):
+ pass
diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/test_taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/test_taxjar_settings.py
new file mode 100644
index 0000000..7cdfd00
--- /dev/null
+++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/test_taxjar_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestTaxJarSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py
new file mode 100644
index 0000000..633692d
--- /dev/null
+++ b/erpnext/erpnext_integrations/taxjar_integration.py
@@ -0,0 +1,251 @@
+import traceback
+
+import pycountry
+import taxjar
+
+import frappe
+from erpnext import get_default_company
+from frappe import _
+from frappe.contacts.doctype.address.address import get_company_address
+
+TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
+SHIP_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "shipping_account_head")
+TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
+TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax")
+SUPPORTED_COUNTRY_CODES = ["AT", "AU", "BE", "BG", "CA", "CY", "CZ", "DE", "DK", "EE", "ES", "FI",
+ "FR", "GB", "GR", "HR", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "NL", "PL", "PT", "RO",
+ "SE", "SI", "SK", "US"]
+
+
+def get_client():
+ taxjar_settings = frappe.get_single("TaxJar Settings")
+
+ if not taxjar_settings.is_sandbox:
+ api_key = taxjar_settings.api_key and taxjar_settings.get_password("api_key")
+ api_url = taxjar.DEFAULT_API_URL
+ else:
+ api_key = taxjar_settings.sandbox_api_key and taxjar_settings.get_password("sandbox_api_key")
+ api_url = taxjar.SANDBOX_API_URL
+
+ if api_key and api_url:
+ return taxjar.Client(api_key=api_key, api_url=api_url)
+
+
+def create_transaction(doc, method):
+ """Create an order transaction in TaxJar"""
+
+ if not TAXJAR_CREATE_TRANSACTIONS:
+ return
+
+ client = get_client()
+
+ if not client:
+ return
+
+ sales_tax = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == TAX_ACCOUNT_HEAD])
+
+ if not sales_tax:
+ return
+
+ tax_dict = get_tax_data(doc)
+
+ if not tax_dict:
+ return
+
+ tax_dict['transaction_id'] = doc.name
+ tax_dict['transaction_date'] = frappe.utils.today()
+ tax_dict['sales_tax'] = sales_tax
+ tax_dict['amount'] = doc.total + tax_dict['shipping']
+
+ try:
+ client.create_order(tax_dict)
+ except taxjar.exceptions.TaxJarResponseError as err:
+ frappe.throw(_(sanitize_error_response(err)))
+ except Exception as ex:
+ print(traceback.format_exc(ex))
+
+
+def delete_transaction(doc, method):
+ """Delete an existing TaxJar order transaction"""
+
+ if not TAXJAR_CREATE_TRANSACTIONS:
+ return
+
+ client = get_client()
+
+ if not client:
+ return
+
+ client.delete_order(doc.name)
+
+
+def get_tax_data(doc):
+ from_address = get_company_address_details(doc)
+ from_shipping_state = from_address.get("state")
+ from_country_code = frappe.db.get_value("Country", from_address.country, "code")
+ from_country_code = from_country_code.upper()
+
+ to_address = get_shipping_address_details(doc)
+ to_shipping_state = to_address.get("state")
+ to_country_code = frappe.db.get_value("Country", to_address.country, "code")
+ to_country_code = to_country_code.upper()
+
+ if to_country_code not in SUPPORTED_COUNTRY_CODES:
+ return
+
+ shipping = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == SHIP_ACCOUNT_HEAD])
+
+ if to_shipping_state is not None:
+ to_shipping_state = get_iso_3166_2_state_code(to_address)
+
+ tax_dict = {
+ 'from_country': from_country_code,
+ 'from_zip': from_address.pincode,
+ 'from_state': from_shipping_state,
+ 'from_city': from_address.city,
+ 'from_street': from_address.address_line1,
+ 'to_country': to_country_code,
+ 'to_zip': to_address.pincode,
+ 'to_city': to_address.city,
+ 'to_street': to_address.address_line1,
+ 'to_state': to_shipping_state,
+ 'shipping': shipping,
+ 'amount': doc.net_total
+ }
+
+ return tax_dict
+
+
+def set_sales_tax(doc, method):
+ if not TAXJAR_CALCULATE_TAX:
+ return
+
+ if not doc.items:
+ return
+
+ # if the party is exempt from sales tax, then set all tax account heads to zero
+ sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \
+ or frappe.db.has_column("Customer", "exempt_from_sales_tax") and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax")
+
+ if sales_tax_exempted:
+ for tax in doc.taxes:
+ if tax.account_head == TAX_ACCOUNT_HEAD:
+ tax.tax_amount = 0
+ break
+
+ doc.run_method("calculate_taxes_and_totals")
+ return
+
+ tax_dict = get_tax_data(doc)
+
+ if not tax_dict:
+ # Remove existing tax rows if address is changed from a taxable state/country
+ setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD])
+ return
+
+ tax_data = validate_tax_request(tax_dict)
+
+ if tax_data is not None:
+ if not tax_data.amount_to_collect:
+ setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD])
+ elif tax_data.amount_to_collect > 0:
+ # Loop through tax rows for existing Sales Tax entry
+ # If none are found, add a row with the tax amount
+ for tax in doc.taxes:
+ if tax.account_head == TAX_ACCOUNT_HEAD:
+ tax.tax_amount = tax_data.amount_to_collect
+
+ doc.run_method("calculate_taxes_and_totals")
+ break
+ else:
+ doc.append("taxes", {
+ "charge_type": "Actual",
+ "description": "Sales Tax",
+ "account_head": TAX_ACCOUNT_HEAD,
+ "tax_amount": tax_data.amount_to_collect
+ })
+
+ doc.run_method("calculate_taxes_and_totals")
+
+
+def validate_tax_request(tax_dict):
+ """Return the sales tax that should be collected for a given order."""
+
+ client = get_client()
+
+ if not client:
+ return
+
+ try:
+ tax_data = client.tax_for_order(tax_dict)
+ except taxjar.exceptions.TaxJarResponseError as err:
+ frappe.throw(_(sanitize_error_response(err)))
+ else:
+ return tax_data
+
+
+def get_company_address_details(doc):
+ """Return default company address details"""
+
+ company_address = get_company_address(get_default_company()).company_address
+
+ if not company_address:
+ frappe.throw(_("Please set a default company address"))
+
+ company_address = frappe.get_doc("Address", company_address)
+ return company_address
+
+
+def get_shipping_address_details(doc):
+ """Return customer shipping address details"""
+
+ if doc.shipping_address_name:
+ shipping_address = frappe.get_doc("Address", doc.shipping_address_name)
+ else:
+ shipping_address = get_company_address_details(doc)
+
+ return shipping_address
+
+
+def get_iso_3166_2_state_code(address):
+ country_code = frappe.db.get_value("Country", address.get("country"), "code")
+
+ error_message = _("""{0} is not a valid state! Check for typos or enter the ISO code for your state.""").format(address.get("state"))
+ state = address.get("state").upper().strip()
+
+ # The max length for ISO state codes is 3, excluding the country code
+ if len(state) <= 3:
+ # PyCountry returns state code as {country_code}-{state-code} (e.g. US-FL)
+ address_state = (country_code + "-" + state).upper()
+
+ states = pycountry.subdivisions.get(country_code=country_code.upper())
+ states = [pystate.code for pystate in states]
+
+ if address_state in states:
+ return state
+
+ frappe.throw(_(error_message))
+ else:
+ try:
+ lookup_state = pycountry.subdivisions.lookup(state)
+ except LookupError:
+ frappe.throw(_(error_message))
+ else:
+ return lookup_state.code.split('-')[1]
+
+
+def sanitize_error_response(response):
+ response = response.full_response.get("detail")
+ response = response.replace("_", " ")
+
+ sanitized_responses = {
+ "to zip": "Zipcode",
+ "to city": "City",
+ "to state": "State",
+ "to country": "Country"
+ }
+
+ for k, v in sanitized_responses.items():
+ response = response.replace(k, v)
+
+ return response
diff --git a/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json b/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json
new file mode 100644
index 0000000..a59f149
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "Clinical Procedures",
+ "chart_type": "Group By",
+ "creation": "2020-07-14 18:17:54.601236",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Clinical Procedure",
+ "dynamic_filters_json": "[[\"Clinical Procedure\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Clinical Procedure\",\"docstatus\",\"=\",\"1\",false]]",
+ "group_by_based_on": "procedure_template",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 13:22:47.008622",
+ "modified": "2020-07-22 13:36:48.114479",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Clinical Procedures",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Percentage",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json b/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json
new file mode 100644
index 0000000..6d560f7
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "Clinical Procedure Status",
+ "chart_type": "Group By",
+ "creation": "2020-07-14 18:17:54.654325",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Clinical Procedure",
+ "dynamic_filters_json": "[[\"Clinical Procedure\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Clinical Procedure\",\"docstatus\",\"=\",\"1\",false]]",
+ "group_by_based_on": "status",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 13:22:46.691764",
+ "modified": "2020-07-22 13:40:17.215775",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Clinical Procedures Status",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Pie",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json b/erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json
new file mode 100644
index 0000000..b24bb34
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json
@@ -0,0 +1,25 @@
+{
+ "chart_name": "Department wise Patient Appointments",
+ "chart_type": "Custom",
+ "creation": "2020-07-17 11:25:37.190130",
+ "custom_options": "{\"colors\": [\"#7CD5FA\", \"#5F62F6\", \"#7544E2\", \"#EE5555\"], \"barOptions\": {\"stacked\": 1}, \"height\": 300}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}",
+ "filters_json": "{}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 15:32:05.827566",
+ "modified": "2020-07-22 15:35:12.798035",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Department wise Patient Appointments",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "source": "Department wise Patient Appointments",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json b/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json
new file mode 100644
index 0000000..0195aac
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json
@@ -0,0 +1,25 @@
+{
+ "chart_name": "Diagnoses",
+ "chart_type": "Group By",
+ "creation": "2020-07-14 18:17:54.705698",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Patient Encounter Diagnosis",
+ "filters_json": "[]",
+ "group_by_based_on": "diagnosis",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 13:22:47.895521",
+ "modified": "2020-07-22 13:43:32.369481",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Diagnoses",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Percentage",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json b/erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json
new file mode 100644
index 0000000..77b47c9
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "In-Patient Status",
+ "chart_type": "Group By",
+ "creation": "2020-07-14 18:17:54.629199",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Inpatient Record",
+ "dynamic_filters_json": "[[\"Inpatient Record\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[]",
+ "group_by_based_on": "status",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 13:22:46.792131",
+ "modified": "2020-07-22 13:33:16.008150",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "In-Patient Status",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json b/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json
new file mode 100644
index 0000000..0524835
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "Lab Tests",
+ "chart_type": "Group By",
+ "creation": "2020-07-14 18:17:54.574903",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Lab Test",
+ "dynamic_filters_json": "[[\"Lab Test\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Lab Test\",\"docstatus\",\"=\",\"1\",false]]",
+ "group_by_based_on": "template",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 13:22:47.344055",
+ "modified": "2020-07-22 13:37:34.490129",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Lab Tests",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Percentage",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json b/erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json
new file mode 100644
index 0000000..19bfb72
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json
@@ -0,0 +1,27 @@
+{
+ "based_on": "appointment_datetime",
+ "chart_name": "Patient Appointments",
+ "chart_type": "Count",
+ "creation": "2020-07-14 18:17:54.525082",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Patient Appointment",
+ "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Patient Appointment\",\"status\",\"!=\",\"Cancelled\",false]]",
+ "idx": 0,
+ "is_public": 0,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 13:22:46.830491",
+ "modified": "2020-07-22 13:38:02.254190",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Patient Appointments",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Daily",
+ "timeseries": 1,
+ "timespan": "Last Month",
+ "type": "Line",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json b/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json
new file mode 100644
index 0000000..8fc86a1
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "Symptoms",
+ "chart_type": "Group By",
+ "creation": "2020-07-14 18:17:54.680852",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Patient Encounter Symptom",
+ "dynamic_filters_json": "",
+ "filters_json": "[]",
+ "group_by_based_on": "complaint",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 13:22:47.296748",
+ "modified": "2020-07-22 13:40:59.655129",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Symptoms",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "timeseries": 0,
+ "type": "Percentage",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_fixtures.py b/erpnext/healthcare/dashboard_fixtures.py
deleted file mode 100644
index 94668a1..0000000
--- a/erpnext/healthcare/dashboard_fixtures.py
+++ /dev/null
@@ -1,245 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-import json
-from frappe import _
-
-def get_data():
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards(),
- })
-
-def get_company():
- company = frappe.defaults.get_defaults().company
- if company:
- return company
- else:
- company = frappe.get_list("Company", limit=1)
- if company:
- return company[0].name
- return None
-
-def get_dashboards():
- return [{
- "name": "Healthcare",
- "dashboard_name": "Healthcare",
- "charts": [
- { "chart": "Patient Appointments", "width": "Full"},
- { "chart": "In-Patient Status", "width": "Half"},
- { "chart": "Clinical Procedures Status", "width": "Half"},
- { "chart": "Lab Tests", "width": "Half"},
- { "chart": "Clinical Procedures", "width": "Half"},
- { "chart": "Symptoms", "width": "Half"},
- { "chart": "Diagnoses", "width": "Half"},
- { "chart": "Department wise Patient Appointments", "width": "Full"}
- ],
- "cards": [
- { "card": "Total Patients" },
- { "card": "Total Patient Admitted" },
- { "card": "Open Appointments" },
- { "card": "Appointments to Bill" }
- ]
- }]
-
-def get_charts():
- company = get_company()
- return [
- {
- "doctype": "Dashboard Chart",
- "time_interval": "Daily",
- "name": "Patient Appointments",
- "chart_name": _("Patient Appointments"),
- "timespan": "Last Month",
- "filters_json": json.dumps([
- ["Patient Appointment", "company", "=", company, False],
- ["Patient Appointment", "status", "!=", "Cancelled"]
- ]),
- "chart_type": "Count",
- "timeseries": 1,
- "based_on": "appointment_datetime",
- "owner": "Administrator",
- "document_type": "Patient Appointment",
- "type": "Line",
- "width": "Half"
- },
- {
- "doctype": "Dashboard Chart",
- "name": "Department wise Patient Appointments",
- "chart_name": _("Department wise Patient Appointments"),
- "chart_type": "Custom",
- "source": "Department wise Patient Appointments",
- "filters_json": json.dumps([]),
- 'is_public': 1,
- "owner": "Administrator",
- "type": "Bar",
- "width": "Full",
- "custom_options": json.dumps({
- "colors": ["#7CD5FA", "#5F62F6", "#7544E2", "#EE5555"],
- "barOptions":{
- "stacked":1
- },
- "height": 300
- })
- },
- {
- "doctype": "Dashboard Chart",
- "name": "Lab Tests",
- "chart_name": _("Lab Tests"),
- "chart_type": "Group By",
- "document_type": "Lab Test",
- "group_by_type": "Count",
- "group_by_based_on": "template",
- "filters_json": json.dumps([
- ["Lab Test", "company", "=", company, False],
- ["Lab Test", "docstatus", "=", 1]
- ]),
- 'is_public': 1,
- "owner": "Administrator",
- "type": "Percentage",
- "width": "Half",
- },
- {
- "doctype": "Dashboard Chart",
- "name": "Clinical Procedures",
- "chart_name": _("Clinical Procedures"),
- "chart_type": "Group By",
- "document_type": "Clinical Procedure",
- "group_by_type": "Count",
- "group_by_based_on": "procedure_template",
- "filters_json": json.dumps([
- ["Clinical Procedure", "company", "=", company, False],
- ["Clinical Procedure", "docstatus", "=", 1]
- ]),
- 'is_public': 1,
- "owner": "Administrator",
- "type": "Percentage",
- "width": "Half",
- },
- {
- "doctype": "Dashboard Chart",
- "name": "In-Patient Status",
- "chart_name": _("In-Patient Status"),
- "chart_type": "Group By",
- "document_type": "Inpatient Record",
- "group_by_type": "Count",
- "group_by_based_on": "status",
- "filters_json": json.dumps([
- ["Inpatient Record", "company", "=", company, False]
- ]),
- 'is_public': 1,
- "owner": "Administrator",
- "type": "Bar",
- "width": "Half",
- },
- {
- "doctype": "Dashboard Chart",
- "name": "Clinical Procedures Status",
- "chart_name": _("Clinical Procedure Status"),
- "chart_type": "Group By",
- "document_type": "Clinical Procedure",
- "group_by_type": "Count",
- "group_by_based_on": "status",
- "filters_json": json.dumps([
- ["Clinical Procedure", "company", "=", company, False],
- ["Clinical Procedure", "docstatus", "=", 1]
- ]),
- 'is_public': 1,
- "owner": "Administrator",
- "type": "Pie",
- "width": "Half",
- },
- {
- "doctype": "Dashboard Chart",
- "name": "Symptoms",
- "chart_name": _("Symptoms"),
- "chart_type": "Group By",
- "document_type": "Patient Encounter Symptom",
- "group_by_type": "Count",
- "group_by_based_on": "complaint",
- "filters_json": json.dumps([]),
- 'is_public': 1,
- "owner": "Administrator",
- "type": "Percentage",
- "width": "Half",
- },
- {
- "doctype": "Dashboard Chart",
- "name": "Diagnoses",
- "chart_name": _("Diagnoses"),
- "chart_type": "Group By",
- "document_type": "Patient Encounter Diagnosis",
- "group_by_type": "Count",
- "group_by_based_on": "diagnosis",
- "filters_json": json.dumps([]),
- 'is_public': 1,
- "owner": "Administrator",
- "type": "Percentage",
- "width": "Half",
- }
- ]
-
-def get_number_cards():
- company = get_company()
- return [
- {
- "name": "Total Patients",
- "label": _("Total Patients"),
- "function": "Count",
- "doctype": "Number Card",
- "document_type": "Patient",
- "filters_json": json.dumps(
- [["Patient","status","=","Active",False]]
- ),
- "is_public": 1,
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Daily"
- },
- {
- "name": "Total Patients Admitted",
- "label": _("Total Patients Admitted"),
- "function": "Count",
- "doctype": "Number Card",
- "document_type": "Patient",
- "filters_json": json.dumps(
- [["Patient","inpatient_status","=","Admitted",False]]
- ),
- "is_public": 1,
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Daily"
- },
- {
- "name": "Open Appointments",
- "label": _("Open Appointments"),
- "function": "Count",
- "doctype": "Number Card",
- "document_type": "Patient Appointment",
- "filters_json": json.dumps(
- [["Patient Appointment","company","=",company,False],
- ["Patient Appointment","status","=","Open",False]]
- ),
- "is_public": 1,
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Daily"
- },
- {
- "name": "Appointments to Bill",
- "label": _("Appointments To Bill"),
- "function": "Count",
- "doctype": "Number Card",
- "document_type": "Patient Appointment",
- "filters_json": json.dumps(
- [["Patient Appointment","company","=",company,False],
- ["Patient Appointment","invoiced","=",0,False]]
- ),
- "is_public": 1,
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Daily"
- }
- ]
\ No newline at end of file
diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json
index 334b655..6546b08 100644
--- a/erpnext/healthcare/desk_page/healthcare/healthcare.json
+++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json
@@ -38,7 +38,7 @@
{
"hidden": 0,
"label": "Records and History",
- "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]"
+ "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]"
},
{
"hidden": 0,
@@ -64,7 +64,7 @@
"idx": 0,
"is_standard": 1,
"label": "Healthcare",
- "modified": "2020-05-28 19:02:28.824995",
+ "modified": "2020-06-25 23:50:56.951698",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare",
diff --git a/erpnext/healthcare/doctype/antibiotic/antibiotic.json b/erpnext/healthcare/doctype/antibiotic/antibiotic.json
index d481036..41a3e31 100644
--- a/erpnext/healthcare/doctype/antibiotic/antibiotic.json
+++ b/erpnext/healthcare/doctype/antibiotic/antibiotic.json
@@ -1,115 +1,151 @@
{
- "allow_copy": 1,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "field:antibiotic_name",
- "beta": 1,
- "creation": "2016-02-23 11:11:30.749731",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "allow_copy": 1,
+ "allow_events_in_timeline": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:antibiotic_name",
+ "beta": 1,
+ "creation": "2016-02-23 11:11:30.749731",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 0,
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "antibiotic_name",
- "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": "Antibiotic Name",
- "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
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "antibiotic_name",
+ "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": "Antibiotic Name",
+ "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": 1
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "abbr",
+ "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": "Abbr",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-08-31 13:44:43.199657",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Antibiotic",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "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": "2019-10-01 17:58:23.136498",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Antibiotic",
+ "name_case": "",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 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": "Healthcare Administrator",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "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": "Healthcare Administrator",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Laboratory User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "amend": 0,
+ "cancel": 0,
+ "create": 0,
+ "delete": 0,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Laboratory User",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
"write": 0
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Healthcare",
- "search_fields": "antibiotic_name",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "antibiotic_name",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Healthcare",
+ "search_fields": "antibiotic_name",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "antibiotic_name",
+ "track_changes": 0,
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json
index eaf8d80..b1d62da 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json
@@ -11,6 +11,7 @@
"title",
"appointment",
"procedure_template",
+ "medical_code",
"column_break_30",
"company",
"invoiced",
@@ -290,11 +291,19 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fetch_from": "procedure_template.medical_code",
+ "fieldname": "medical_code",
+ "fieldtype": "Link",
+ "label": "Medical Code",
+ "options": "Medical Code",
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-27 21:36:23.796924",
+ "modified": "2020-06-29 14:28:11.779815",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Clinical Procedure",
diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
index 16d4540..1ef110d 100644
--- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
+++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
@@ -30,6 +30,16 @@
mark_change_in_item(frm);
},
+ medical_code: function(frm) {
+ frm.set_query("medical_code", function() {
+ return {
+ filters: {
+ medical_code_standard: frm.doc.medical_code_standard
+ }
+ };
+ });
+ },
+
refresh: function(frm) {
frm.fields_dict['items'].grid.set_column_disp('barcode', false);
frm.fields_dict['items'].grid.set_column_disp('batch_no', false);
diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json
index 9cfd682..17ac7eb 100644
--- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json
+++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json
@@ -21,6 +21,9 @@
"is_billable",
"rate",
"medical_department",
+ "medical_coding_section",
+ "medical_code_standard",
+ "medical_code",
"consumables",
"consume_stock",
"items",
@@ -46,7 +49,6 @@
"fieldname": "item_code",
"fieldtype": "Data",
"label": "Item Code",
- "options": "Item",
"read_only_depends_on": "eval: !doc.__islocal ",
"reqd": 1
},
@@ -173,10 +175,29 @@
"no_copy": 1,
"options": "Item",
"read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "medical_coding_section",
+ "fieldtype": "Section Break",
+ "label": "Medical Coding"
+ },
+ {
+ "fieldname": "medical_code_standard",
+ "fieldtype": "Link",
+ "label": "Medical Code Standard",
+ "options": "Medical Code Standard"
+ },
+ {
+ "depends_on": "medical_code_standard",
+ "fieldname": "medical_code",
+ "fieldtype": "Link",
+ "label": "Medical Code",
+ "options": "Medical Code"
}
],
"links": [],
- "modified": "2020-02-28 14:16:13.184981",
+ "modified": "2020-06-29 14:12:27.158130",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Clinical Procedure Template",
diff --git a/erpnext/healthcare/doctype/lab_test_groups/__init__.py b/erpnext/healthcare/doctype/descriptive_test_result/__init__.py
similarity index 100%
copy from erpnext/healthcare/doctype/lab_test_groups/__init__.py
copy to erpnext/healthcare/doctype/descriptive_test_result/__init__.py
diff --git a/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json b/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json
new file mode 100644
index 0000000..fcd3828
--- /dev/null
+++ b/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json
@@ -0,0 +1,74 @@
+{
+ "actions": [],
+ "allow_copy": 1,
+ "beta": 1,
+ "creation": "2016-02-22 15:12:36.202380",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "lab_test_particulars",
+ "result_value",
+ "allow_blank",
+ "template",
+ "require_result_value"
+ ],
+ "fields": [
+ {
+ "fieldname": "lab_test_particulars",
+ "fieldtype": "Data",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Particulars",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.require_result_value == 1",
+ "fieldname": "result_value",
+ "fieldtype": "Small Text",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Value"
+ },
+ {
+ "fieldname": "template",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Template",
+ "options": "Lab Test Template",
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "require_result_value",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Require Result Value",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "allow_blank",
+ "fieldtype": "Check",
+ "label": "Allow Blank",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-07-23 12:33:47.693065",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Descriptive Test Result",
+ "owner": "Administrator",
+ "permissions": [],
+ "restrict_to_domain": "Healthcare",
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py b/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.py
similarity index 83%
copy from erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py
copy to erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.py
index 35c8efd..7ccf6b5 100644
--- a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py
+++ b/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.py
@@ -5,5 +5,5 @@
from __future__ import unicode_literals
from frappe.model.document import Document
-class SensitivityTestItems(Document):
+class DescriptiveTestResult(Document):
pass
diff --git a/erpnext/healthcare/doctype/special_test_template/__init__.py b/erpnext/healthcare/doctype/descriptive_test_template/__init__.py
similarity index 100%
rename from erpnext/healthcare/doctype/special_test_template/__init__.py
rename to erpnext/healthcare/doctype/descriptive_test_template/__init__.py
diff --git a/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json b/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json
new file mode 100644
index 0000000..9ee8f4f
--- /dev/null
+++ b/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json
@@ -0,0 +1,41 @@
+{
+ "actions": [],
+ "allow_copy": 1,
+ "beta": 1,
+ "creation": "2016-02-22 16:12:12.394200",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "particulars",
+ "allow_blank"
+ ],
+ "fields": [
+ {
+ "fieldname": "particulars",
+ "fieldtype": "Data",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Result Component"
+ },
+ {
+ "default": "0",
+ "fieldname": "allow_blank",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Allow Blank"
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-06-24 14:03:51.728863",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Descriptive Test Template",
+ "owner": "Administrator",
+ "permissions": [],
+ "restrict_to_domain": "Healthcare",
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/special_test_template/special_test_template.py b/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py
similarity index 83%
copy from erpnext/healthcare/doctype/special_test_template/special_test_template.py
copy to erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py
index e4e0d5b..281f32d 100644
--- a/erpnext/healthcare/doctype/special_test_template/special_test_template.py
+++ b/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py
@@ -5,5 +5,5 @@
from __future__ import unicode_literals
from frappe.model.document import Document
-class SpecialTestTemplate(Document):
+class DescriptiveTestTemplate(Document):
pass
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
index 3dc7c1e..5da5a06 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
@@ -71,6 +71,7 @@
frappe.throw(_(msg))
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
fields = ['name', 'practitioner_name', 'mobile_phone']
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
index 2f0115c..0104386 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
@@ -39,8 +39,8 @@
"create_lab_test_on_si_submit",
"create_sample_collection_for_lab_test",
"column_break_34",
- "employee_name_and_designation_in_print",
"lab_test_approval_required",
+ "employee_name_and_designation_in_print",
"custom_signature_in_print",
"laboratory_sms_alerts",
"sms_printed",
@@ -306,7 +306,7 @@
],
"issingle": 1,
"links": [],
- "modified": "2020-03-26 11:25:21.842092",
+ "modified": "2020-07-08 15:17:21.543218",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Settings",
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
index cf63b65..b4a4e29 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
@@ -217,6 +217,7 @@
inpatient_record.save(ignore_permissions = True)
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_leave_from(doctype, txt, searchfield, start, page_len, filters):
docname = filters['docname']
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js
index bf1ecc8..8036c7d 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.js
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.js
@@ -1,49 +1,53 @@
// Copyright (c) 2016, ESS and contributors
// For license information, please see license.txt
-cur_frm.cscript.custom_refresh = function(doc) {
- cur_frm.toggle_display("sb_sensitivity", doc.sensitivity_toggle=="1");
- cur_frm.toggle_display("sb_special", doc.special_toggle=="1");
- cur_frm.toggle_display("sb_normal", doc.normal_toggle=="1");
+cur_frm.cscript.custom_refresh = function (doc) {
+ cur_frm.toggle_display('sb_sensitivity', doc.sensitivity_toggle);
+ cur_frm.toggle_display('organisms_section', doc.descriptive_toggle);
+ cur_frm.toggle_display('sb_descriptive', doc.descriptive_toggle);
+ cur_frm.toggle_display('sb_normal', doc.normal_toggle);
};
frappe.ui.form.on('Lab Test', {
- setup: function(frm) {
+ setup: function (frm) {
frm.get_field('normal_test_items').grid.editable_fields = [
- {fieldname: 'lab_test_name', columns: 3},
- {fieldname: 'lab_test_event', columns: 2},
- {fieldname: 'result_value', columns: 2},
- {fieldname: 'lab_test_uom', columns: 1},
- {fieldname: 'normal_range', columns: 2}
+ { fieldname: 'lab_test_name', columns: 3 },
+ { fieldname: 'lab_test_event', columns: 2 },
+ { fieldname: 'result_value', columns: 2 },
+ { fieldname: 'lab_test_uom', columns: 1 },
+ { fieldname: 'normal_range', columns: 2 }
];
- frm.get_field('special_test_items').grid.editable_fields = [
- {fieldname: 'lab_test_particulars', columns: 3},
- {fieldname: 'result_value', columns: 7}
+ frm.get_field('descriptive_test_items').grid.editable_fields = [
+ { fieldname: 'lab_test_particulars', columns: 3 },
+ { fieldname: 'result_value', columns: 7 }
];
},
- refresh : function(frm){
+ refresh: function (frm) {
refresh_field('normal_test_items');
- refresh_field('special_test_items');
- if(frm.doc.__islocal){
+ refresh_field('descriptive_test_items');
+ if (frm.doc.__islocal) {
frm.add_custom_button(__('Get from Patient Encounter'), function () {
get_lab_test_prescribed(frm);
});
}
- if(frm.doc.docstatus==1 && frm.doc.status!='Approved' && frm.doc.status!='Rejected' && frappe.defaults.get_default("lab_test_approval_required") && frappe.user.has_role("LabTest Approver")){
- frm.add_custom_button(__('Approve'), function() {
- status_update(1,frm);
- });
- frm.add_custom_button(__('Reject'), function() {
- status_update(0,frm);
- });
+ if (frappe.defaults.get_default('lab_test_approval_required') && frappe.user.has_role('LabTest Approver')) {
+ if (frm.doc.docstatus === 1 && frm.doc.status !== 'Approved' && frm.doc.status !== 'Rejected') {
+ frm.add_custom_button(__('Approve'), function () {
+ status_update(1, frm);
+ });
+ frm.add_custom_button(__('Reject'), function () {
+ status_update(0, frm);
+ });
+ }
}
- if(frm.doc.docstatus==1 && frm.doc.sms_sent==0){
- frm.add_custom_button(__('Send SMS'), function() {
+
+ if (frm.doc.docstatus === 1 && frm.doc.sms_sent === 0 && frm.doc.status !== 'Rejected' ) {
+ frm.add_custom_button(__('Send SMS'), function () {
frappe.call({
- method: "erpnext.healthcare.doctype.healthcare_settings.healthcare_settings.get_sms_text",
- args:{doc: frm.doc.name},
- callback: function(r) {
- if(!r.exc) {
+ method: 'erpnext.healthcare.doctype.healthcare_settings.healthcare_settings.get_sms_text',
+ args: { doc: frm.doc.name },
+ callback: function (r) {
+ if (!r.exc) {
var emailed = r.message.emailed;
var printed = r.message.printed;
make_dialog(frm, emailed, printed);
@@ -53,246 +57,223 @@
});
}
- },
- onload: function (frm) {
- frm.add_fetch("practitioner", "department", "department");
- if(frm.doc.employee){
- frappe.call({
- method: "frappe.client.get",
- args:{
- doctype: "Employee",
- name: frm.doc.employee
- },
- callback: function(arg){
- frappe.model.set_value(frm.doctype,frm.docname,"employee_name", arg.message.employee_name);
- frappe.model.set_value(frm.doctype,frm.docname,"employee_designation", arg.message.designation);
- }
- });
- }
}
});
-frappe.ui.form.on("Lab Test", "patient", function(frm) {
- if(frm.doc.patient){
+frappe.ui.form.on('Lab Test', 'patient', function (frm) {
+ if (frm.doc.patient) {
frappe.call({
- "method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail",
- args: {
- patient: frm.doc.patient
- },
+ 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
+ args: { patient: frm.doc.patient },
callback: function (data) {
var age = null;
- if(data.message.dob){
+ if (data.message.dob) {
age = calculate_age(data.message.dob);
}
- frappe.model.set_value(frm.doctype,frm.docname, "patient_age", age);
- frappe.model.set_value(frm.doctype,frm.docname, "patient_sex", data.message.sex);
- frappe.model.set_value(frm.doctype,frm.docname, "email", data.message.email);
- frappe.model.set_value(frm.doctype,frm.docname, "mobile", data.message.mobile);
- frappe.model.set_value(frm.doctype,frm.docname, "report_preference", data.message.report_preference);
+ let values = {
+ 'patient_age': age,
+ 'patient_sex': data.message.sex,
+ 'email': data.message.email,
+ 'mobile': data.message.mobile,
+ 'report_preference': data.message.report_preference
+ };
+ frm.set_value(values);
}
});
}
});
-frappe.ui.form.on('Normal Test Items', {
- normal_test_items_remove: function() {
- frappe.msgprint(__("Not permitted, configure Lab Test Template as required"));
+frappe.ui.form.on('Normal Test Result', {
+ normal_test_items_remove: function () {
+ frappe.msgprint(__('Not permitted, configure Lab Test Template as required'));
cur_frm.reload_doc();
}
});
-frappe.ui.form.on('Special Test Items', {
- special_test_items_remove: function() {
- frappe.msgprint(__("Not permitted, configure Lab Test Template as required"));
+frappe.ui.form.on('Descriptive Test Result', {
+ descriptive_test_items_remove: function () {
+ frappe.msgprint(__('Not permitted, configure Lab Test Template as required'));
cur_frm.reload_doc();
}
});
-var status_update = function(approve,frm){
+var status_update = function (approve, frm) {
var doc = frm.doc;
var status = null;
- if(approve == 1){
- status = "Approved";
+ if (approve == 1) {
+ status = 'Approved';
}
else {
- status = "Rejected";
+ status = 'Rejected';
}
frappe.call({
- method: "erpnext.healthcare.doctype.lab_test.lab_test.update_status",
- args: {status: status, name: doc.name},
- callback: function(){
+ method: 'erpnext.healthcare.doctype.lab_test.lab_test.update_status',
+ args: { status: status, name: doc.name },
+ callback: function () {
cur_frm.reload_doc();
}
});
};
-var get_lab_test_prescribed = function(frm){
- if(frm.doc.patient){
+var get_lab_test_prescribed = function (frm) {
+ if (frm.doc.patient) {
frappe.call({
- method: "erpnext.healthcare.doctype.lab_test.lab_test.get_lab_test_prescribed",
- args: {patient: frm.doc.patient},
- callback: function(r){
+ method: 'erpnext.healthcare.doctype.lab_test.lab_test.get_lab_test_prescribed',
+ args: { patient: frm.doc.patient },
+ callback: function (r) {
show_lab_tests(frm, r.message);
}
});
}
- else{
- frappe.msgprint(__("Please select a Patient to get Lab Tests"));
+ else {
+ frappe.msgprint(__('Please select Patient to get Lab Tests'));
}
};
-var show_lab_tests = function(frm, result){
+var show_lab_tests = function (frm, lab_test_list) {
var d = new frappe.ui.Dialog({
- title: __("Lab Tests"),
- fields: [
- {
- fieldtype: "HTML", fieldname: "lab_test"
- }
- ]
+ title: __('Lab Tests'),
+ fields: [{
+ fieldtype: 'HTML', fieldname: 'lab_test'
+ }]
});
var html_field = d.fields_dict.lab_test.$wrapper;
html_field.empty();
- $.each(result, function(x, y){
- var row = $(repl('<div class="col-xs-12" style="padding-top:12px; text-align:center;" >\
- <div class="col-xs-2"> %(lab_test)s </div>\
- <div class="col-xs-2"> %(encounter)s </div>\
- <div class="col-xs-3"> %(practitioner)s </div>\
- <div class="col-xs-3"> %(date)s </div>\
- <div class="col-xs-1">\
- <a data-name="%(name)s" data-lab-test="%(lab_test)s"\
- data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\
- data-invoiced="%(invoiced)s" href="#"><button class="btn btn-default btn-xs">Get Lab Tests\
- </button></a></div></div>', {name:y[0], lab_test: y[1], encounter:y[2], invoiced:y[3], practitioner:y[4], date:y[5]})).appendTo(html_field);
- row.find("a").click(function() {
- frm.doc.template = $(this).attr("data-lab-test");
- frm.doc.prescription = $(this).attr("data-name");
- frm.doc.practitioner = $(this).attr("data-practitioner");
- frm.set_df_property("template", "read_only", 1);
- frm.set_df_property("patient", "read_only", 1);
- frm.set_df_property("practitioner", "read_only", 1);
+ $.each(lab_test_list, function (x, y) {
+ var row = $(repl(
+ '<div class="col-xs-12" style="padding-top:12px;">\
+ <div class="col-xs-3"> %(lab_test)s </div>\
+ <div class="col-xs-4"> %(practitioner_name)s<br>%(encounter)s</div>\
+ <div class="col-xs-3"> %(date)s </div>\
+ <div class="col-xs-1">\
+ <a data-name="%(name)s" data-lab-test="%(lab_test)s"\
+ data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\
+ data-invoiced="%(invoiced)s" href="#"><button class="btn btn-default btn-xs">Get</button></a>\
+ </div>\
+ </div><hr>',
+ { name: y[0], lab_test: y[1], encounter: y[2], invoiced: y[3], practitioner: y[4], practitioner_name: y[5], date: y[6] })
+ ).appendTo(html_field);
+
+ row.find("a").click(function () {
+ frm.doc.template = $(this).attr('data-lab-test');
+ frm.doc.prescription = $(this).attr('data-name');
+ frm.doc.practitioner = $(this).attr('data-practitioner');
+ frm.set_df_property('template', 'read_only', 1);
+ frm.set_df_property('patient', 'read_only', 1);
+ frm.set_df_property('practitioner', 'read_only', 1);
frm.doc.invoiced = 0;
- if($(this).attr("data-invoiced") == 1){
+ if ($(this).attr('data-invoiced') === 1) {
frm.doc.invoiced = 1;
}
- refresh_field("invoiced");
- refresh_field("template");
+ refresh_field('invoiced');
+ refresh_field('template');
d.hide();
return false;
});
});
- if(!result.length){
- var msg = __("No Lab Tests found for the Patient {0}", [frm.doc.patient_name.bold()]);
+ if (!lab_test_list.length) {
+ var msg = __('No Lab Tests found for the Patient {0}', [frm.doc.patient_name.bold()]);
html_field.empty();
- $(repl('<div class="col-xs-12" style="padding-top:0px;" >%(msg)s</div>', {msg: msg})).appendTo(html_field);
+ $(repl('<div class="col-xs-12" style="padding-top:0px;" >%(msg)s</div>', { msg: msg })).appendTo(html_field);
}
d.show();
};
-cur_frm.cscript.custom_before_submit = function(doc) {
- if(doc.normal_test_items){
- for(let result in doc.normal_test_items){
- if(!doc.normal_test_items[result].result_value && doc.normal_test_items[result].require_result_value == 1){
- frappe.msgprint(__("Please input all required Result Value(s)"));
- throw("Error");
+cur_frm.cscript.custom_before_submit = function (doc) {
+ if (doc.normal_test_items) {
+ for (let result in doc.normal_test_items) {
+ if (!doc.normal_test_items[result].result_value && !doc.normal_test_items[result].allow_blank && doc.normal_test_items[result].require_result_value) {
+ frappe.throw(__('Please input all required result values'));
}
}
}
- if(doc.special_test_items){
- for(let result in doc.special_test_items){
- if(!doc.special_test_items[result].result_value && doc.special_test_items[result].require_result_value == 1){
- frappe.msgprint(__("Please input all required Result Value(s)"));
- throw("Error");
+ if (doc.descriptive_test_items) {
+ for (let result in doc.descriptive_test_items) {
+ if (!doc.descriptive_test_items[result].result_value && !doc.descriptive_test_items[result].allow_blank && doc.descriptive_test_items[result].require_result_value) {
+ frappe.throw(__('Please input all required result values'));
}
}
}
};
-var make_dialog = function(frm, emailed, printed) {
+var make_dialog = function (frm, emailed, printed) {
var number = frm.doc.mobile;
var dialog = new frappe.ui.Dialog({
title: 'Send SMS',
width: 400,
fields: [
- {fieldname:'sms_type', fieldtype:'Select', label:'Type', options:
- ['Emailed','Printed']},
- {fieldname:'number', fieldtype:'Data', label:'Mobile Number', reqd:1},
- {fieldname:'messages_label', fieldtype:'HTML'},
- {fieldname:'messages', fieldtype:'HTML', reqd:1}
+ { fieldname: 'sms_type', fieldtype: 'Select', label: 'Type', options: ['Emailed', 'Printed'] },
+ { fieldname: 'number', fieldtype: 'Data', label: 'Mobile Number', reqd: 1 },
+ { fieldname: 'message', fieldtype: 'Small Text', label: 'Message', reqd: 1 }
],
- primary_action_label: __("Send"),
- primary_action : function(){
+ primary_action_label: __('Send'),
+ primary_action: function () {
var values = dialog.fields_dict;
- if(!values){
+ if (!values) {
return;
}
- send_sms(values,frm);
+ send_sms(values, frm);
dialog.hide();
}
});
- if(frm.doc.report_preference == "Email"){
+ if (frm.doc.report_preference == 'Print') {
dialog.set_values({
- 'sms_type': "Emailed",
- 'number': number
+ 'sms_type': 'Printed',
+ 'number': number,
+ 'message': printed
});
- dialog.fields_dict.messages_label.html("Message".bold());
- dialog.fields_dict.messages.html(emailed);
- }else{
+ } else {
dialog.set_values({
- 'sms_type': "Printed",
- 'number': number
+ 'sms_type': 'Emailed',
+ 'number': number,
+ 'message': emailed
});
- dialog.fields_dict.messages_label.html("Message".bold());
- dialog.fields_dict.messages.html(printed);
}
var fd = dialog.fields_dict;
- $(fd.sms_type.input).change(function(){
- if(dialog.get_value('sms_type') == 'Emailed'){
+ $(fd.sms_type.input).change(function () {
+ if (dialog.get_value('sms_type') == 'Emailed') {
dialog.set_values({
- 'number': number
+ 'number': number,
+ 'message': emailed
});
- fd.messages_label.html("Message".bold());
- fd.messages.html(emailed);
- }else{
+ } else {
dialog.set_values({
- 'number': number
+ 'number': number,
+ 'message': printed
});
- fd.messages_label.html("Message".bold());
- fd.messages.html(printed);
}
});
dialog.show();
};
-var send_sms = function(v,frm){
- var doc = frm.doc;
- var number = v.number.last_value;
- var messages = v.messages.wrapper.innerText;
+var send_sms = function (vals, frm) {
+ var number = vals.number.value;
+ var message = vals.message.last_value;
+
+ if (!number || !message) {
+ frappe.throw(__('Did not send SMS, missing patient mobile number or message content.'));
+ }
frappe.call({
- method: "frappe.core.doctype.sms_settings.sms_settings.send_sms",
+ method: 'frappe.core.doctype.sms_settings.sms_settings.send_sms',
args: {
receiver_list: [number],
- msg: messages
+ msg: message
},
- callback: function(r) {
- if(r.exc) {frappe.msgprint(r.exc); return; }
- else{
- frappe.call({
- method: "erpnext.healthcare.doctype.lab_test.lab_test.update_lab_test_print_sms_email_status",
- args: {print_sms_email: "sms_sent", name: doc.name},
- callback: function(){
- cur_frm.reload_doc();
- }
- });
+ callback: function (r) {
+ if (r.exc) {
+ frappe.msgprint(r.exc);
+ } else {
+ frm.reload_doc();
}
}
});
};
-var calculate_age = function(birth) {
- var ageMS = Date.parse(Date()) - Date.parse(birth);
- var age = new Date();
+var calculate_age = function (dob) {
+ var ageMS = Date.parse(Date()) - Date.parse(dob);
+ var age = new Date();
age.setTime(ageMS);
- var years = age.getFullYear() - 1970;
- return years + " Year(s) " + age.getMonth() + " Month(s) " + age.getDate() + " Day(s)";
+ var years = age.getFullYear() - 1970;
+ return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)';
};
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json
index 17dc1ed..2eb8014 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.json
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.json
@@ -10,48 +10,63 @@
"engine": "InnoDB",
"field_order": [
"naming_series",
+ "template",
+ "lab_test_name",
+ "lab_test_group",
+ "medical_code",
+ "department",
+ "column_break_26",
+ "company",
+ "status",
+ "submitted_date",
+ "result_date",
+ "approved_date",
+ "expected_result_date",
+ "expected_result_time",
+ "printed_on",
+ "invoiced",
+ "sb_first",
"patient",
"patient_name",
"patient_age",
"patient_sex",
+ "inpatient_record",
"report_preference",
"email",
"mobile",
- "practitioner",
"c_b",
- "inpatient_record",
- "company",
- "department",
- "status",
- "submitted_date",
- "approved_date",
- "sample",
- "result_date",
+ "practitioner",
+ "practitioner_name",
+ "requesting_department",
"employee",
"employee_name",
"employee_designation",
"user",
- "invoiced",
- "sb_first",
- "lab_test_name",
- "column_break_26",
- "template",
- "lab_test_group",
+ "sample",
"sb_normal",
+ "lab_test_html",
"normal_test_items",
- "sb_special",
- "special_test_items",
+ "sb_descriptive",
+ "descriptive_test_items",
+ "organisms_section",
+ "organism_test_items",
"sb_sensitivity",
"sensitivity_test_items",
"sb_comments",
"lab_test_comment",
"sb_customresult",
"custom_result",
+ "worksheet_section",
+ "worksheet_instructions",
+ "result_legend_section",
+ "legend_print_position",
+ "result_legend",
+ "section_break_50",
"email_sent",
"sms_sent",
"printed",
"normal_toggle",
- "special_toggle",
+ "descriptive_toggle",
"sensitivity_toggle",
"amended_from",
"prescription"
@@ -88,7 +103,6 @@
"fieldname": "patient",
"fieldtype": "Link",
"ignore_user_permissions": 1,
- "in_list_view": 1,
"in_standard_filter": 1,
"label": "Patient",
"options": "Patient",
@@ -119,6 +133,7 @@
"label": "Gender",
"options": "Gender",
"print_hide": 1,
+ "read_only": 1,
"report_hide": 1,
"reqd": 1,
"set_only_once": 1
@@ -127,11 +142,14 @@
"fieldname": "practitioner",
"fieldtype": "Link",
"ignore_user_permissions": 1,
- "label": "Healthcare Practitioner",
+ "in_list_view": 1,
+ "label": "Requesting Practitioner",
+ "no_copy": 1,
"options": "Healthcare Practitioner",
"search_index": 1
},
{
+ "fetch_from": "patient.email",
"fieldname": "email",
"fieldtype": "Data",
"hidden": 1,
@@ -141,6 +159,7 @@
"report_hide": 1
},
{
+ "fetch_from": "patient.mobile",
"fieldname": "mobile",
"fieldtype": "Data",
"hidden": 1,
@@ -165,21 +184,23 @@
"print_hide": 1
},
{
+ "fetch_from": "template.department",
"fieldname": "department",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"in_standard_filter": 1,
"label": "Department",
"options": "Medical Department",
+ "read_only": 1,
"search_index": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
- "hidden": 1,
"label": "Status",
"options": "Draft\nCompleted\nApproved\nRejected\nCancelled",
"print_hide": 1,
+ "read_only": 1,
"report_hide": 1,
"search_index": 1
},
@@ -211,15 +232,38 @@
"report_hide": 1
},
{
+ "default": "Today",
+ "fieldname": "expected_result_date",
+ "fieldtype": "Date",
+ "hidden": 1,
+ "label": "Expected Result Date",
+ "read_only": 1
+ },
+ {
+ "fieldname": "expected_result_time",
+ "fieldtype": "Time",
+ "hidden": 1,
+ "label": "Expected Result Time",
+ "read_only": 1
+ },
+ {
"fieldname": "result_date",
"fieldtype": "Date",
+ "hidden": 1,
"label": "Result Date",
"search_index": 1
},
{
+ "allow_on_submit": 1,
+ "fieldname": "printed_on",
+ "fieldtype": "Datetime",
+ "label": "Printed on",
+ "read_only": 1
+ },
+ {
"fieldname": "employee",
"fieldtype": "Link",
- "label": "Lab Technician",
+ "label": "Employee (Lab Technician)",
"no_copy": 1,
"options": "Employee",
"print_hide": 1,
@@ -229,7 +273,7 @@
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
- "label": "Technician Name",
+ "label": "Lab Technician Name",
"no_copy": 1,
"print_hide": 1,
"read_only": 1,
@@ -239,7 +283,7 @@
"fetch_from": "employee.designation",
"fieldname": "employee_designation",
"fieldtype": "Data",
- "label": "Designation",
+ "label": "Lab Technician Designation",
"no_copy": 1,
"print_hide": 1,
"read_only": 1,
@@ -256,6 +300,7 @@
"report_hide": 1
},
{
+ "fetch_from": "patient.report_preference",
"fieldname": "report_preference",
"fieldtype": "Data",
"label": "Report Preference",
@@ -271,7 +316,6 @@
"fieldname": "lab_test_name",
"fieldtype": "Data",
"in_list_view": 1,
- "in_standard_filter": 1,
"label": "Test Name",
"no_copy": 1,
"print_hide": 1,
@@ -280,13 +324,10 @@
"search_index": 1
},
{
- "fieldname": "column_break_26",
- "fieldtype": "Column Break"
- },
- {
"fieldname": "template",
"fieldtype": "Link",
"ignore_user_permissions": 1,
+ "in_standard_filter": 1,
"label": "Test Template",
"options": "Lab Test Template",
"print_hide": 1,
@@ -304,33 +345,40 @@
"report_hide": 1
},
{
+ "fetch_from": "template.medical_code",
+ "fieldname": "medical_code",
+ "fieldtype": "Link",
+ "label": "Medical Code",
+ "options": "Medical Code",
+ "read_only": 1
+ },
+ {
"fieldname": "sb_normal",
"fieldtype": "Section Break"
},
{
"fieldname": "normal_test_items",
"fieldtype": "Table",
- "options": "Normal Test Items"
+ "options": "Normal Test Result",
+ "print_hide": 1
},
{
- "fieldname": "sb_special",
+ "fieldname": "lab_test_html",
+ "fieldtype": "HTML"
+ },
+ {
+ "depends_on": "descriptive_toggle",
+ "fieldname": "organisms_section",
"fieldtype": "Section Break"
},
{
- "fieldname": "special_test_items",
- "fieldtype": "Table",
- "options": "Special Test Items",
- "print_hide": 1,
- "report_hide": 1
- },
- {
"fieldname": "sb_sensitivity",
"fieldtype": "Section Break"
},
{
"fieldname": "sensitivity_test_items",
"fieldtype": "Table",
- "options": "Sensitivity Test Items",
+ "options": "Sensitivity Test Result",
"print_hide": 1,
"report_hide": 1
},
@@ -342,7 +390,8 @@
"fieldname": "lab_test_comment",
"fieldtype": "Text",
"ignore_xss_filter": 1,
- "label": "Comments"
+ "label": "Comments",
+ "print_hide": 1
},
{
"collapsible": 1,
@@ -354,7 +403,8 @@
"fieldname": "custom_result",
"fieldtype": "Text Editor",
"ignore_xss_filter": 1,
- "label": "Custom Result"
+ "label": "Custom Result",
+ "print_hide": 1
},
{
"default": "0",
@@ -390,14 +440,6 @@
},
{
"default": "0",
- "fieldname": "special_toggle",
- "fieldtype": "Check",
- "hidden": 1,
- "print_hide": 1,
- "report_hide": 1
- },
- {
- "default": "0",
"fieldname": "sensitivity_toggle",
"fieldtype": "Check",
"hidden": 1,
@@ -424,11 +466,91 @@
"print_hide": 1,
"read_only": 1,
"report_hide": 1
+ },
+ {
+ "fieldname": "column_break_26",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "practitioner.department",
+ "fieldname": "requesting_department",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Requesting Department",
+ "options": "Medical Department",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "practitioner.practitioner_name",
+ "fieldname": "practitioner_name",
+ "fieldtype": "Data",
+ "label": "Requesting Practitioner",
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "result_legend_section",
+ "fieldtype": "Section Break",
+ "label": "Result Legend Print"
+ },
+ {
+ "fieldname": "legend_print_position",
+ "fieldtype": "Select",
+ "label": "Print Position",
+ "options": "\nBottom\nTop\nBoth",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "result_legend",
+ "fieldtype": "Text Editor",
+ "label": "Result Legend",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "section_break_50",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "worksheet_instructions",
+ "fieldtype": "Text Editor",
+ "label": "Worksheet Instructions",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "worksheet_section",
+ "fieldtype": "Section Break",
+ "label": "Worksheet Print"
+ },
+ {
+ "fieldname": "descriptive_test_items",
+ "fieldtype": "Table",
+ "options": "Descriptive Test Result",
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "fieldname": "sb_descriptive",
+ "fieldtype": "Section Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "descriptive_toggle",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "fieldname": "organism_test_items",
+ "fieldtype": "Table",
+ "options": "Organism Test Result",
+ "print_hide": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-04 19:16:29.131168",
+ "modified": "2020-07-16 13:35:24.811062",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Test",
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py
index b2c5e6b..865f4a1 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.py
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.py
@@ -10,26 +10,30 @@
class LabTest(Document):
def on_submit(self):
- frappe.db.set_value(self.doctype,self.name,"submitted_date", getdate())
+ self.db_set('submitted_date', getdate())
+ self.db_set('status', 'Completed')
insert_lab_test_to_medical_record(self)
- frappe.db.set_value("Lab Test", self.name, "status", "Completed")
def on_cancel(self):
delete_lab_test_from_medical_record(self)
- frappe.db.set_value("Lab Test", self.name, "status", "Cancelled")
+ self.db_set('status', 'Cancelled')
self.reload()
+ def validate(self):
+ if not self.is_new():
+ self.set_secondary_uom_result()
+
def on_update(self):
- if(self.sensitivity_test_items):
+ if self.sensitivity_test_items:
sensitivity = sorted(self.sensitivity_test_items, key=lambda x: x.antibiotic_sensitivity)
for i, item in enumerate(sensitivity):
- item.idx = i+1
+ item.idx = i + 1
self.sensitivity_test_items = sensitivity
def after_insert(self):
- if(self.prescription):
- frappe.db.set_value("Lab Prescription", self.prescription, "lab_test_created", 1)
- if frappe.db.get_value("Lab Prescription", self.prescription, 'invoiced') == 1:
+ if self.prescription:
+ frappe.db.set_value('Lab Prescription', self.prescription, 'lab_test_created', 1)
+ if frappe.db.get_value('Lab Prescription', self.prescription, 'invoiced'):
self.invoiced = True
if not self.lab_test_name and self.template:
self.load_test_from_template()
@@ -40,109 +44,110 @@
create_test_from_template(lab_test)
self.reload()
+ def set_secondary_uom_result(self):
+ for item in self.normal_test_items:
+ if item.result_value and item.secondary_uom and item.conversion_factor:
+ try:
+ item.secondary_uom_result = float(item.result_value) * float(item.conversion_factor)
+ except:
+ item.secondary_uom_result = ''
+ frappe.msgprint(_('Result for Secondary UOM not calculated for row #{0}'.format(item.idx)), title = _('Warning'))
+
+
def create_test_from_template(lab_test):
- template = frappe.get_doc("Lab Test Template", lab_test.template)
- patient = frappe.get_doc("Patient", lab_test.patient)
+ template = frappe.get_doc('Lab Test Template', lab_test.template)
+ patient = frappe.get_doc('Patient', lab_test.patient)
lab_test.lab_test_name = template.lab_test_name
lab_test.result_date = getdate()
lab_test.department = template.department
lab_test.lab_test_group = template.lab_test_group
+ lab_test.legend_print_position = template.legend_print_position
+ lab_test.result_legend = template.result_legend
+ lab_test.worksheet_instructions = template.worksheet_instructions
lab_test = create_sample_collection(lab_test, template, patient, None)
lab_test = load_result_format(lab_test, template, None, None)
@frappe.whitelist()
def update_status(status, name):
- frappe.db.sql("""update `tabLab Test` set status=%s, approved_date=%s where name = %s""", (status, getdate(), name))
-
-@frappe.whitelist()
-def update_lab_test_print_sms_email_status(print_sms_email, name):
- frappe.db.set_value("Lab Test",name,print_sms_email,1)
+ if name and status:
+ frappe.db.set_value('Lab Test', name, {
+ 'status': status,
+ 'approved_date': getdate()
+ })
@frappe.whitelist()
def create_multiple(doctype, docname):
+ if not doctype or not docname:
+ frappe.throw(_('Sales Invoice or Patient Encounter is required to create Lab Tests'), title=_('Insufficient Data'))
+
lab_test_created = False
- if doctype == "Sales Invoice":
+ if doctype == 'Sales Invoice':
lab_test_created = create_lab_test_from_invoice(docname)
- elif doctype == "Patient Encounter":
+ elif doctype == 'Patient Encounter':
lab_test_created = create_lab_test_from_encounter(docname)
if lab_test_created:
- frappe.msgprint(_("Lab Test(s) {0} created".format(lab_test_created)))
+ frappe.msgprint(_('Lab Test(s) {0} created'.format(lab_test_created)))
else:
- frappe.msgprint(_("No Lab Tests created"))
+ frappe.msgprint(_('No Lab Tests created'))
-def create_lab_test_from_encounter(encounter_id):
+def create_lab_test_from_encounter(encounter):
lab_test_created = False
- encounter = frappe.get_doc("Patient Encounter", encounter_id)
+ encounter = frappe.get_doc('Patient Encounter', encounter)
- lab_test_ids = frappe.db.sql("""select lp.name, lp.lab_test_code, lp.invoiced
- from `tabPatient Encounter` et, `tabLab Prescription` lp
- where et.patient=%s and lp.parent=%s and
- lp.parent=et.name and lp.lab_test_created=0 and et.docstatus=1""", (encounter.patient, encounter_id))
-
- if lab_test_ids:
- patient = frappe.get_doc("Patient", encounter.patient)
- for lab_test_id in lab_test_ids:
- template = get_lab_test_template(lab_test_id[1])
- if template:
- lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template, encounter.company)
- lab_test.save(ignore_permissions = True)
- frappe.db.set_value("Lab Prescription", lab_test_id[0], "lab_test_created", 1)
- if not lab_test_created:
- lab_test_created = lab_test.name
- else:
- lab_test_created += ", "+lab_test.name
+ if encounter and encounter.lab_test_prescription:
+ patient = frappe.get_doc('Patient', encounter.patient)
+ for item in encounter.lab_test_prescription:
+ if not item.lab_test_created:
+ template = get_lab_test_template(item.lab_test_code)
+ if template:
+ lab_test = create_lab_test_doc(item.invoiced, encounter.practitioner, patient, template, encounter.company)
+ lab_test.save(ignore_permissions = True)
+ frappe.db.set_value('Lab Prescription', item.name, 'lab_test_created', 1)
+ if not lab_test_created:
+ lab_test_created = lab_test.name
+ else:
+ lab_test_created += ', ' + lab_test.name
return lab_test_created
-def create_lab_test_from_invoice(invoice_name):
+def create_lab_test_from_invoice(sales_invoice):
lab_tests_created = False
- invoice = frappe.get_doc("Sales Invoice", invoice_name)
- if invoice.patient:
- patient = frappe.get_doc("Patient", invoice.patient)
+ invoice = frappe.get_doc('Sales Invoice', sales_invoice)
+ if invoice and invoice.patient:
+ patient = frappe.get_doc('Patient', invoice.patient)
for item in invoice.items:
lab_test_created = 0
- if item.reference_dt == "Lab Prescription":
- lab_test_created = frappe.db.get_value("Lab Prescription", item.reference_dn, "lab_test_created")
- elif item.reference_dt == "Lab Test":
+ if item.reference_dt == 'Lab Prescription':
+ lab_test_created = frappe.db.get_value('Lab Prescription', item.reference_dn, 'lab_test_created')
+ elif item.reference_dt == 'Lab Test':
lab_test_created = 1
if lab_test_created != 1:
template = get_lab_test_template(item.item_code)
if template:
lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template, invoice.company)
- if item.reference_dt == "Lab Prescription":
+ if item.reference_dt == 'Lab Prescription':
lab_test.prescription = item.reference_dn
lab_test.save(ignore_permissions = True)
- if item.reference_dt != "Lab Prescription":
- frappe.db.set_value("Sales Invoice Item", item.name, "reference_dt", "Lab Test")
- frappe.db.set_value("Sales Invoice Item", item.name, "reference_dn", lab_test.name)
+ if item.reference_dt != 'Lab Prescription':
+ frappe.db.set_value('Sales Invoice Item', item.name, 'reference_dt', 'Lab Test')
+ frappe.db.set_value('Sales Invoice Item', item.name, 'reference_dn', lab_test.name)
if not lab_tests_created:
lab_tests_created = lab_test.name
else:
- lab_tests_created += ", " + lab_test.name
+ lab_tests_created += ', ' + lab_test.name
return lab_tests_created
def get_lab_test_template(item):
- template_id = check_template_exists(item)
+ template_id = frappe.db.exists('Lab Test Template', {'item': item})
if template_id:
- return frappe.get_doc("Lab Test Template", template_id)
- return False
-
-def check_template_exists(item):
- template_exists = frappe.db.exists(
- "Lab Test Template",
- {
- 'item': item
- }
- )
- if template_exists:
- return template_exists
+ return frappe.get_doc('Lab Test Template', template_id)
return False
def create_lab_test_doc(invoiced, practitioner, patient, template, company):
- lab_test = frappe.new_doc("Lab Test")
+ lab_test = frappe.new_doc('Lab Test')
lab_test.invoiced = invoiced
lab_test.practitioner = practitioner
lab_test.patient = patient.name
@@ -159,63 +164,71 @@
return lab_test
def create_normals(template, lab_test):
- lab_test.normal_toggle = "1"
- normal = lab_test.append("normal_test_items")
+ lab_test.normal_toggle = 1
+ normal = lab_test.append('normal_test_items')
normal.lab_test_name = template.lab_test_name
normal.lab_test_uom = template.lab_test_uom
+ normal.secondary_uom = template.secondary_uom
+ normal.conversion_factor = template.conversion_factor
normal.normal_range = template.lab_test_normal_range
normal.require_result_value = 1
+ normal.allow_blank = 0
normal.template = template.name
def create_compounds(template, lab_test, is_group):
- lab_test.normal_toggle = "1"
+ lab_test.normal_toggle = 1
for normal_test_template in template.normal_test_templates:
- normal = lab_test.append("normal_test_items")
+ normal = lab_test.append('normal_test_items')
if is_group:
normal.lab_test_event = normal_test_template.lab_test_event
else:
normal.lab_test_name = normal_test_template.lab_test_event
normal.lab_test_uom = normal_test_template.lab_test_uom
+ normal.secondary_uom = normal_test_template.secondary_uom
+ normal.conversion_factor = normal_test_template.conversion_factor
normal.normal_range = normal_test_template.normal_range
normal.require_result_value = 1
+ normal.allow_blank = normal_test_template.allow_blank
normal.template = template.name
-def create_specials(template, lab_test):
- lab_test.special_toggle = "1"
- if(template.sensitivity):
- lab_test.sensitivity_toggle = "1"
- for special_test_template in template.special_test_template:
- special = lab_test.append("special_test_items")
- special.lab_test_particulars = special_test_template.particulars
- special.require_result_value = 1
- special.template = template.name
+def create_descriptives(template, lab_test):
+ lab_test.descriptive_toggle = 1
+ if template.sensitivity:
+ lab_test.sensitivity_toggle = 1
+ for descriptive_test_template in template.descriptive_test_templates:
+ descriptive = lab_test.append('descriptive_test_items')
+ descriptive.lab_test_particulars = descriptive_test_template.particulars
+ descriptive.require_result_value = 1
+ descriptive.allow_blank = descriptive_test_template.allow_blank
+ descriptive.template = template.name
def create_sample_doc(template, patient, invoice, company = None):
if template.sample:
sample_exists = frappe.db.exists({
- "doctype": "Sample Collection",
- "patient": patient.name,
- "docstatus": 0,
- "sample": template.sample
+ 'doctype': 'Sample Collection',
+ 'patient': patient.name,
+ 'docstatus': 0,
+ 'sample': template.sample
})
if sample_exists:
- # update Sample Collection by adding quantity
- sample_collection = frappe.get_doc("Sample Collection", sample_exists[0][0])
+ # Update Sample Collection by adding quantity
+ sample_collection = frappe.get_doc('Sample Collection', sample_exists[0][0])
quantity = int(sample_collection.sample_qty) + int(template.sample_qty)
if template.sample_details:
- sample_details = sample_collection.sample_details + "\n==============\n" + _("Test: ")
- sample_details += (template.get("lab_test_name") or template.get("template")) + "\n"
- sample_details += _("Collection Details: ") + "\n\t" + template.sample_details
+ sample_details = sample_collection.sample_details + '\n-\n' + _('Test: ')
+ sample_details += (template.get('lab_test_name') or template.get('template')) + '\n'
+ sample_details += _('Collection Details: ') + '\n\t' + template.sample_details
+ frappe.db.set_value('Sample Collection', sample_collection.name, 'sample_details', sample_details)
- frappe.db.set_value("Sample Collection", sample_collection.name, "sample_details", sample_details)
- frappe.db.set_value("Sample Collection", sample_collection.name, "sample_qty", quantity)
+ frappe.db.set_value('Sample Collection', sample_collection.name, 'sample_qty', quantity)
else:
- #create Sample Collection for template, copy vals from Invoice
- sample_collection = frappe.new_doc("Sample Collection")
- if(invoice):
+ # Create Sample Collection for template, copy vals from Invoice
+ sample_collection = frappe.new_doc('Sample Collection')
+ if invoice:
sample_collection.invoiced = True
+
sample_collection.patient = patient.name
sample_collection.patient_age = patient.get_age()
sample_collection.patient_sex = patient.sex
@@ -224,125 +237,146 @@
sample_collection.sample_qty = template.sample_qty
sample_collection.company = company
- if(template.sample_details):
- sample_collection.sample_details = "Test :" + (template.get("lab_test_name") or template.get("template")) +"\n"+"Collection Detials:\n\t"+template.sample_details
+ if template.sample_details:
+ sample_collection.sample_details = 'Test :' + (template.get('lab_test_name') or template.get('template')) + '\n' + 'Collection Detials:\n\t' + template.sample_details
sample_collection.save(ignore_permissions=True)
return sample_collection
def create_sample_collection(lab_test, template, patient, invoice):
- if(frappe.db.get_value("Healthcare Settings", None, "create_sample_collection_for_lab_test") == "1"):
+ if frappe.get_cached_value('Healthcare Settings', None, 'create_sample_collection_for_lab_test'):
sample_collection = create_sample_doc(template, patient, invoice, lab_test.company)
- if(sample_collection):
+ if sample_collection:
lab_test.sample = sample_collection.name
+
return lab_test
def load_result_format(lab_test, template, prescription, invoice):
- if(template.lab_test_template_type == 'Single'):
+ if template.lab_test_template_type == 'Single':
create_normals(template, lab_test)
- elif(template.lab_test_template_type == 'Compound'):
+ elif template.lab_test_template_type == 'Compound':
create_compounds(template, lab_test, False)
- elif(template.lab_test_template_type == 'Descriptive'):
- create_specials(template, lab_test)
- elif(template.lab_test_template_type == 'Grouped'):
- #iterate for each template in the group and create one result for all.
+ elif template.lab_test_template_type == 'Descriptive':
+ create_descriptives(template, lab_test)
+ elif template.lab_test_template_type == 'Grouped':
+ # Iterate for each template in the group and create one result for all.
for lab_test_group in template.lab_test_groups:
- #template_in_group = None
- if(lab_test_group.lab_test_template):
- template_in_group = frappe.get_doc("Lab Test Template",
+ # Template_in_group = None
+ if lab_test_group.lab_test_template:
+ template_in_group = frappe.get_doc('Lab Test Template',
lab_test_group.lab_test_template)
- if(template_in_group):
- if(template_in_group.lab_test_template_type == 'Single'):
+ if template_in_group:
+ if template_in_group.lab_test_template_type == 'Single':
create_normals(template_in_group, lab_test)
- elif(template_in_group.lab_test_template_type == 'Compound'):
- normal_heading = lab_test.append("normal_test_items")
+ elif template_in_group.lab_test_template_type == 'Compound':
+ normal_heading = lab_test.append('normal_test_items')
normal_heading.lab_test_name = template_in_group.lab_test_name
normal_heading.require_result_value = 0
+ normal_heading.allow_blank = 1
normal_heading.template = template_in_group.name
create_compounds(template_in_group, lab_test, True)
- elif(template_in_group.lab_test_template_type == 'Descriptive'):
- special_heading = lab_test.append("special_test_items")
- special_heading.lab_test_name = template_in_group.lab_test_name
- special_heading.require_result_value = 0
- special_heading.template = template_in_group.name
- create_specials(template_in_group, lab_test)
- else:
- normal = lab_test.append("normal_test_items")
+ elif template_in_group.lab_test_template_type == 'Descriptive':
+ descriptive_heading = lab_test.append('descriptive_test_items')
+ descriptive_heading.lab_test_name = template_in_group.lab_test_name
+ descriptive_heading.require_result_value = 0
+ descriptive_heading.allow_blank = 1
+ descriptive_heading.template = template_in_group.name
+ create_descriptives(template_in_group, lab_test)
+ else: # Lab Test Group - Add New Line
+ normal = lab_test.append('normal_test_items')
normal.lab_test_name = lab_test_group.group_event
normal.lab_test_uom = lab_test_group.group_test_uom
+ normal.secondary_uom = lab_test_group.secondary_uom
+ normal.conversion_factor = lab_test_group.conversion_factor
normal.normal_range = lab_test_group.group_test_normal_range
+ normal.allow_blank = lab_test_group.allow_blank
normal.require_result_value = 1
normal.template = template.name
- if(template.lab_test_template_type != 'No Result'):
- if(prescription):
+ if template.lab_test_template_type != 'No Result':
+ if prescription:
lab_test.prescription = prescription
- if(invoice):
- frappe.db.set_value("Lab Prescription", prescription, "invoiced", True)
- lab_test.save(ignore_permissions=True) # insert the result
+ if invoice:
+ frappe.db.set_value('Lab Prescription', prescription, 'invoiced', True)
+ lab_test.save(ignore_permissions=True) # Insert the result
return lab_test
@frappe.whitelist()
def get_employee_by_user_id(user_id):
- emp_id = frappe.db.get_value("Employee",{"user_id":user_id})
- employee = frappe.get_doc("Employee",emp_id)
+ emp_id = frappe.db.get_value('Employee', { 'user_id': user_id })
+ employee = frappe.get_doc('Employee', emp_id)
return employee
def insert_lab_test_to_medical_record(doc):
table_row = False
subject = cstr(doc.lab_test_name)
if doc.practitioner:
- subject += frappe.bold(_("Healthcare Practitioner: "))+ doc.practitioner + "<br>"
+ subject += frappe.bold(_('Healthcare Practitioner: '))+ doc.practitioner + '<br>'
if doc.normal_test_items:
item = doc.normal_test_items[0]
- comment = ""
+ comment = ''
if item.lab_test_comment:
comment = str(item.lab_test_comment)
- table_row = frappe.bold(_("Lab Test Conducted: ")) + item.lab_test_name
+ table_row = frappe.bold(_('Lab Test Conducted: ')) + item.lab_test_name
if item.lab_test_event:
- table_row += frappe.bold(_("Lab Test Event: ")) + item.lab_test_event
+ table_row += frappe.bold(_('Lab Test Event: ')) + item.lab_test_event
if item.result_value:
- table_row += " " + frappe.bold(_("Lab Test Result: ")) + item.result_value
+ table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value
if item.normal_range:
- table_row += " " + _("Normal Range:") + item.normal_range
- table_row += " " + comment
+ table_row += ' ' + _('Normal Range:') + item.normal_range
+ table_row += ' ' + comment
- elif doc.special_test_items:
- item = doc.special_test_items[0]
+ elif doc.descriptive_test_items:
+ item = doc.descriptive_test_items[0]
if item.lab_test_particulars and item.result_value:
- table_row = item.lab_test_particulars +" "+ item.result_value
+ table_row = item.lab_test_particulars + ' ' + item.result_value
elif doc.sensitivity_test_items:
item = doc.sensitivity_test_items[0]
if item.antibiotic and item.antibiotic_sensitivity:
- table_row = item.antibiotic + " " + item.antibiotic_sensitivity
+ table_row = item.antibiotic + ' ' + item.antibiotic_sensitivity
if table_row:
- subject += "<br>" + table_row
+ subject += '<br>' + table_row
if doc.lab_test_comment:
- subject += "<br>" + cstr(doc.lab_test_comment)
+ subject += '<br>' + cstr(doc.lab_test_comment)
- medical_record = frappe.new_doc("Patient Medical Record")
+ medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
- medical_record.status = "Open"
+ medical_record.status = 'Open'
medical_record.communication_date = doc.result_date
- medical_record.reference_doctype = "Lab Test"
+ medical_record.reference_doctype = 'Lab Test'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
- medical_record.save(ignore_permissions=True)
+ medical_record.save(ignore_permissions = True)
def delete_lab_test_from_medical_record(self):
- medical_record_id = frappe.db.sql("select name from `tabPatient Medical Record` where reference_name=%s",(self.name))
+ medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name= %s', (self.name))
if medical_record_id and medical_record_id[0][0]:
- frappe.delete_doc("Patient Medical Record", medical_record_id[0][0])
+ frappe.delete_doc('Patient Medical Record', medical_record_id[0][0])
@frappe.whitelist()
def get_lab_test_prescribed(patient):
- return frappe.db.sql("""select cp.name, cp.lab_test_code, cp.parent, cp.invoiced, ct.practitioner, ct.encounter_date from `tabPatient Encounter` ct,
- `tabLab Prescription` cp where ct.patient=%s and cp.parent=ct.name and cp.lab_test_created=0""", (patient))
+ return frappe.db.sql(
+ '''
+ select
+ lp.name,
+ lp.lab_test_code,
+ lp.parent,
+ lp.invoiced,
+ pe.practitioner,
+ pe.practitioner_name,
+ pe.encounter_date
+ from
+ `tabPatient Encounter` pe, `tabLab Prescription` lp
+ where
+ pe.patient=%s
+ and lp.parent=pe.name
+ and lp.lab_test_created=0
+ ''', (patient))
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test_list.js b/erpnext/healthcare/doctype/lab_test/lab_test_list.js
index 1f6a12f..6783bb3 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test_list.js
+++ b/erpnext/healthcare/doctype/lab_test/lab_test_list.js
@@ -2,57 +2,63 @@
(c) ESS 2015-16
*/
frappe.listview_settings['Lab Test'] = {
- add_fields: ["name", "status", "invoiced"],
- filters:[["docstatus","=","0"]],
- get_indicator: function(doc) {
- if(doc.status=="Approved"){
- return [__("Approved"), "green", "status,=,Approved"];
+ add_fields: ['name', 'status', 'invoiced'],
+ filters: [['docstatus', '=', '0']],
+ get_indicator: function (doc) {
+ if (doc.status == 'Approved') {
+ return [__('Approved'), 'green', 'status, = ,Approved'];
}
- if(doc.status=="Rejected"){
- return [__("Rejected"), "yellow", "status,=,Rejected"];
+ if (doc.status == 'Rejected') {
+ return [__('Rejected'), 'orange', 'status, =, Rejected'];
}
},
- onload: function(listview) {
- listview.page.add_menu_item(__("Create Multiple"), function() {
+ onload: function (listview) {
+ listview.page.add_menu_item(__('Create Multiple'), function () {
create_multiple_dialog(listview);
});
}
};
-var create_multiple_dialog = function(listview){
+var create_multiple_dialog = function (listview) {
var dialog = new frappe.ui.Dialog({
title: 'Create Multiple Lab Test',
width: 100,
fields: [
- {fieldtype: "Link", label: "Patient", fieldname: "patient", options: "Patient", reqd: 1},
- {fieldtype: "Select", label: "Invoice / Patient Encounter", fieldname: "doctype",
- options: "\nSales Invoice\nPatient Encounter", reqd: 1},
- {fieldtype: "Dynamic Link", fieldname: "docname", options: "doctype", reqd: 1,
- get_query: function(){
+ { fieldtype: 'Link', label: 'Patient', fieldname: 'patient', options: 'Patient', reqd: 1 },
+ {
+ fieldtype: 'Select', label: 'Invoice / Patient Encounter', fieldname: 'doctype',
+ options: '\nSales Invoice\nPatient Encounter', reqd: 1
+ },
+ {
+ fieldtype: 'Dynamic Link', fieldname: 'docname', options: 'doctype', reqd: 1,
+ get_query: function () {
return {
filters: {
- "patient": dialog.get_value("patient"),
- "docstatus": 1
+ 'patient': dialog.get_value('patient'),
+ 'docstatus': 1
}
};
}
}
],
- primary_action_label: __("Create Lab Test"),
- primary_action : function(){
+ primary_action_label: __('Create Lab Test'),
+ primary_action: function () {
frappe.call({
method: 'erpnext.healthcare.doctype.lab_test.lab_test.create_multiple',
- args:{
- 'doctype': dialog.get_value("doctype"),
- 'docname': dialog.get_value("docname")
+ args: {
+ 'doctype': dialog.get_value('doctype'),
+ 'docname': dialog.get_value('docname')
},
- callback: function(data) {
- if(!data.exc){
+ callback: function (data) {
+ if (!data.exc) {
+ if (!data.message) {
+ frappe.msgprint(__('No Lab Tests created'));
+ }
listview.refresh();
}
},
freeze: true,
- freeze_message: "Creating Lab Test..."
+ freeze_message: 'Creating Lab Tests...'
});
dialog.hide();
}
diff --git a/erpnext/healthcare/doctype/lab_test_groups/__init__.py b/erpnext/healthcare/doctype/lab_test_group_template/__init__.py
similarity index 100%
rename from erpnext/healthcare/doctype/lab_test_groups/__init__.py
rename to erpnext/healthcare/doctype/lab_test_group_template/__init__.py
diff --git a/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json
new file mode 100644
index 0000000..beea7a3
--- /dev/null
+++ b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json
@@ -0,0 +1,118 @@
+{
+ "actions": [],
+ "allow_copy": 1,
+ "beta": 1,
+ "creation": "2016-03-29 17:37:29.913583",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "template_or_new_line",
+ "lab_test_template",
+ "lab_test_rate",
+ "lab_test_description",
+ "group_event",
+ "group_test_uom",
+ "secondary_uom",
+ "conversion_factor",
+ "allow_blank",
+ "column_break_8",
+ "group_test_normal_range"
+ ],
+ "fields": [
+ {
+ "default": "Add Test",
+ "fieldname": "template_or_new_line",
+ "fieldtype": "Select",
+ "options": "Add Test\nAdd New Line",
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "depends_on": "eval:doc.template_or_new_line == 'Add Test'",
+ "fieldname": "lab_test_template",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "in_list_view": 1,
+ "label": "Test Name",
+ "options": "Lab Test Template"
+ },
+ {
+ "fetch_from": "lab_test_template.lab_test_rate",
+ "fieldname": "lab_test_rate",
+ "fieldtype": "Currency",
+ "label": "Rate",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1
+ },
+ {
+ "fetch_from": "lab_test_template.lab_test_description",
+ "fieldname": "lab_test_description",
+ "fieldtype": "Data",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Description",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.template_or_new_line == 'Add New Line'",
+ "fieldname": "group_event",
+ "fieldtype": "Data",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Event"
+ },
+ {
+ "depends_on": "eval:doc.template_or_new_line =='Add New Line'",
+ "fieldname": "group_test_uom",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "label": "UOM",
+ "options": "Lab Test UOM"
+ },
+ {
+ "depends_on": "eval:doc.template_or_new_line == 'Add New Line'",
+ "fieldname": "group_test_normal_range",
+ "fieldtype": "Long Text",
+ "ignore_xss_filter": 1,
+ "label": "Normal Range"
+ },
+ {
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "eval:doc.template_or_new_line =='Add New Line'",
+ "fieldname": "secondary_uom",
+ "fieldtype": "Link",
+ "label": "Secondary UOM",
+ "options": "Lab Test UOM"
+ },
+ {
+ "depends_on": "secondary_uom",
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "label": "Conversion Factor"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.template_or_new_line == 'Add New Line'",
+ "fieldname": "allow_blank",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Allow Blank"
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-06-24 10:59:01.921924",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Lab Test Group Template",
+ "owner": "Administrator",
+ "permissions": [],
+ "restrict_to_domain": "Healthcare",
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/special_test_template/special_test_template.py b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.py
similarity index 84%
rename from erpnext/healthcare/doctype/special_test_template/special_test_template.py
rename to erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.py
index e4e0d5b..1e2cef4 100644
--- a/erpnext/healthcare/doctype/special_test_template/special_test_template.py
+++ b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.py
@@ -5,5 +5,5 @@
from __future__ import unicode_literals
from frappe.model.document import Document
-class SpecialTestTemplate(Document):
+class LabTestGroupTemplate(Document):
pass
diff --git a/erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.json b/erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.json
deleted file mode 100644
index e51d8b7..0000000
--- a/erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.json
+++ /dev/null
@@ -1,310 +0,0 @@
-{
- "allow_copy": 1,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 1,
- "creation": "2016-03-29 17:37:29.913583",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Add Test",
- "depends_on": "",
- "fieldname": "template_or_new_line",
- "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": "Add Test\nAdd new line",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.template_or_new_line == 'Add Test'",
- "fieldname": "lab_test_template",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Test Name",
- "length": 0,
- "no_copy": 0,
- "options": "Lab Test Template",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "lab_test_template.lab_test_rate",
- "fieldname": "lab_test_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,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "lab_test_template.lab_test_description",
- "fieldname": "lab_test_description",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Description",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.template_or_new_line == 'Add new line'",
- "fieldname": "group_event",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Event",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.template_or_new_line =='Add new line'",
- "fieldname": "group_test_uom",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "UOM",
- "length": 0,
- "no_copy": 0,
- "options": "Lab Test UOM",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.template_or_new_line == 'Add new line'",
- "fieldname": "group_test_normal_range",
- "fieldtype": "Long Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Normal Range",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "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
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-09-04 09:49:24.817787",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Lab Test Groups",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Healthcare",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js
index 5c9bf49..2e41f51 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js
@@ -1,19 +1,28 @@
// Copyright (c) 2016, ESS
// License: ESS license.txt
-frappe.ui.form.on("Lab Test Template",{
+frappe.ui.form.on('Lab Test Template', {
lab_test_name: function(frm) {
if (!frm.doc.lab_test_code)
- frm.set_value("lab_test_code", frm.doc.lab_test_name);
+ frm.set_value('lab_test_code', frm.doc.lab_test_name);
if (!frm.doc.lab_test_description)
- frm.set_value("lab_test_description", frm.doc.lab_test_name);
+ frm.set_value('lab_test_description', frm.doc.lab_test_name);
},
refresh : function(frm) {
- // Restrict Special, Grouped type templates in Child TestGroups
- frm.set_query("lab_test_template", "lab_test_groups", function() {
+ // Restrict Special, Grouped type templates in Child Test Groups
+ frm.set_query('lab_test_template', 'lab_test_groups', function() {
return {
filters: {
- lab_test_template_type: ['in',['Single','Compound']]
+ lab_test_template_type: ['in', ['Single','Compound']]
+ }
+ };
+ });
+ },
+ medical_code: function(frm) {
+ frm.set_query('medical_code', function() {
+ return {
+ filters: {
+ medical_code_standard: frm.doc.medical_code_standard
}
};
});
@@ -21,10 +30,10 @@
});
cur_frm.cscript.custom_refresh = function(doc) {
- cur_frm.set_df_property("lab_test_code", "read_only", doc.__islocal ? 0 : 1);
+ cur_frm.set_df_property('lab_test_code', 'read_only', doc.__islocal ? 0 : 1);
if (!doc.__islocal) {
- cur_frm.add_custom_button(__("Change Template Code"), function() {
+ cur_frm.add_custom_button(__('Change Template Code'), function() {
change_template_code(doc);
});
}
@@ -32,12 +41,12 @@
let change_template_code = function(doc) {
let d = new frappe.ui.Dialog({
- title:__("Change Template Code"),
+ title:__('Change Template Code'),
fields:[
{
- "fieldtype": "Data",
- "label": "Lab Test Template Code",
- "fieldname": "lab_test_code",
+ 'fieldtype': 'Data',
+ 'label': 'Lab Test Template Code',
+ 'fieldname': 'lab_test_code',
reqd: 1
}
],
@@ -45,49 +54,44 @@
let values = d.get_values();
if (values) {
frappe.call({
- "method": "erpnext.healthcare.doctype.lab_test_template.lab_test_template.change_test_code_from_template",
- "args": {lab_test_code: values.lab_test_code, doc: doc},
+ 'method': 'erpnext.healthcare.doctype.lab_test_template.lab_test_template.change_test_code_from_template',
+ 'args': {lab_test_code: values.lab_test_code, doc: doc},
callback: function (data) {
- frappe.set_route("Form", "Lab Test Template", data.message);
+ frappe.set_route('Form', 'Lab Test Template', data.message);
}
});
}
d.hide();
},
- primary_action_label: __("Change Template Code")
+ primary_action_label: __('Change Template Code')
});
d.show();
d.set_values({
- "lab_test_code": doc.lab_test_code
+ 'lab_test_code': doc.lab_test_code
});
};
-frappe.ui.form.on("Lab Test Template", "lab_test_name", function(frm){
-
+frappe.ui.form.on('Lab Test Template', 'lab_test_name', function(frm) {
frm.doc.change_in_item = 1;
-
-});
-frappe.ui.form.on("Lab Test Template", "lab_test_rate", function(frm){
-
- frm.doc.change_in_item = 1;
-
-});
-frappe.ui.form.on("Lab Test Template", "lab_test_group", function(frm){
-
- frm.doc.change_in_item = 1;
-
-});
-frappe.ui.form.on("Lab Test Template", "lab_test_description", function(frm){
-
- frm.doc.change_in_item = 1;
-
});
-frappe.ui.form.on("Lab Test Groups", "template_or_new_line", function (frm, cdt, cdn) {
+frappe.ui.form.on('Lab Test Template', 'lab_test_rate', function(frm) {
+ frm.doc.change_in_item = 1;
+});
+
+frappe.ui.form.on('Lab Test Template', 'lab_test_group', function(frm) {
+ frm.doc.change_in_item = 1;
+});
+
+frappe.ui.form.on('Lab Test Template', 'lab_test_description', function(frm) {
+ frm.doc.change_in_item = 1;
+});
+
+frappe.ui.form.on('Lab Test Groups', 'template_or_new_line', function (frm, cdt, cdn) {
let child = locals[cdt][cdn];
- if (child.template_or_new_line == "Add new line") {
- frappe.model.set_value(cdt, cdn, 'lab_test_template', "");
- frappe.model.set_value(cdt, cdn, 'lab_test_description', "");
+ if (child.template_or_new_line == 'Add New Line') {
+ frappe.model.set_value(cdt, cdn, 'lab_test_template', '');
+ frappe.model.set_value(cdt, cdn, 'lab_test_description', '');
}
});
diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json
index a606bc4..db64297 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json
@@ -15,28 +15,38 @@
"lab_test_group",
"department",
"column_break_3",
- "lab_test_template_type",
"disabled",
+ "lab_test_template_type",
"is_billable",
"lab_test_rate",
+ "section_break_description",
+ "lab_test_description",
"section_break_normal",
"lab_test_uom",
- "lab_test_normal_range",
+ "secondary_uom",
+ "conversion_factor",
"column_break_10",
+ "lab_test_normal_range",
"section_break_compound",
"normal_test_templates",
"section_break_special",
"sensitivity",
- "special_test_template",
+ "descriptive_test_templates",
"section_break_group",
"lab_test_groups",
- "section_break_description",
- "lab_test_description",
+ "medical_coding_section",
+ "medical_code_standard",
+ "medical_code",
"sb_sample_collection",
"sample",
"sample_uom",
"sample_qty",
"sample_details",
+ "worksheet_section",
+ "worksheet_instructions",
+ "result_legend_section",
+ "legend_print_position",
+ "result_legend",
"change_in_item"
],
"fields": [
@@ -92,7 +102,7 @@
"fieldtype": "Column Break"
},
{
- "description": "Single for results which require only a single input, result UOM and normal value \n<br>\nCompound for results which require multiple input fields with corresponding event names, result UOMs and normal values\n<br>\nDescriptive for tests which have multiple result components and corresponding result entry fields. \n<br>\nGrouped for test templates which are a group of other test templates.\n<br>\nNo Result for tests with no results. Also, no Lab Test is created. e.g.. Sub Tests for Grouped results.",
+ "description": "<b>Single</b>: Results which require only a single input.\n<br>\n<b>Compound</b>: Results which require multiple event inputs.\n<br>\n<b>Descriptive</b>: Tests which have multiple result components with manual result entry.\n<br>\n<b>Grouped</b>: Test templates which are a group of other test templates.\n<br>\n<b>No Result</b>: Tests with no results, can be ordered and billed but no Lab Test will be created. e.g.. Sub Tests for Grouped results",
"fieldname": "lab_test_template_type",
"fieldtype": "Select",
"in_standard_filter": 1,
@@ -118,6 +128,24 @@
"mandatory_depends_on": "eval:doc.is_billable == 1"
},
{
+ "fieldname": "medical_coding_section",
+ "fieldtype": "Section Break",
+ "label": "Medical Coding"
+ },
+ {
+ "depends_on": "medical_code_standard",
+ "fieldname": "medical_code",
+ "fieldtype": "Link",
+ "label": "Medical Code",
+ "options": "Medical Code"
+ },
+ {
+ "fieldname": "medical_code_standard",
+ "fieldtype": "Link",
+ "label": "Medical Code Standard",
+ "options": "Medical Code Standard"
+ },
+ {
"depends_on": "eval:doc.lab_test_template_type == 'Single'",
"fieldname": "section_break_normal",
"fieldtype": "Section Break",
@@ -156,7 +184,7 @@
"depends_on": "eval:doc.lab_test_template_type == 'Descriptive'",
"fieldname": "section_break_special",
"fieldtype": "Section Break",
- "label": "Special"
+ "label": "Descriptive"
},
{
"default": "0",
@@ -165,11 +193,6 @@
"label": "Sensitivity"
},
{
- "fieldname": "special_test_template",
- "fieldtype": "Table",
- "options": "Special Test Template"
- },
- {
"depends_on": "eval:doc.lab_test_template_type == 'Grouped'",
"fieldname": "section_break_group",
"fieldtype": "Section Break",
@@ -178,20 +201,23 @@
{
"fieldname": "lab_test_groups",
"fieldtype": "Table",
- "options": "Lab Test Groups"
+ "options": "Lab Test Group Template"
},
{
+ "collapsible": 1,
"fieldname": "section_break_description",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Description "
},
{
"fieldname": "lab_test_description",
- "fieldtype": "Text",
+ "fieldtype": "Text Editor",
"ignore_xss_filter": 1,
"label": "Description",
"no_copy": 1
},
{
+ "collapsible": 1,
"fieldname": "sb_sample_collection",
"fieldtype": "Section Break",
"label": "Sample Collection"
@@ -234,13 +260,61 @@
},
{
"fieldname": "sample_details",
- "fieldtype": "Text",
+ "fieldtype": "Small Text",
"ignore_xss_filter": 1,
"label": "Collection Details"
+ },
+ {
+ "collapsible": 1,
+ "description": "Information to help easily interpret the test report, will be printed as part of the Lab Test result.",
+ "fieldname": "result_legend_section",
+ "fieldtype": "Section Break",
+ "label": "Result Legend Print"
+ },
+ {
+ "fieldname": "result_legend",
+ "fieldtype": "Text Editor",
+ "label": "Result Legend"
+ },
+ {
+ "fieldname": "legend_print_position",
+ "fieldtype": "Select",
+ "label": "Print Position",
+ "options": "Bottom\nTop\nBoth"
+ },
+ {
+ "fieldname": "secondary_uom",
+ "fieldtype": "Link",
+ "label": "Secondary UOM",
+ "options": "Lab Test UOM"
+ },
+ {
+ "depends_on": "secondary_uom",
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "label": "Conversion Factor",
+ "mandatory_depends_on": "secondary_uom"
+ },
+ {
+ "description": "Instructions to be printed on the worksheet",
+ "fieldname": "worksheet_instructions",
+ "fieldtype": "Text Editor",
+ "label": "Worksheet Instructions"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "worksheet_section",
+ "fieldtype": "Section Break",
+ "label": "Worksheet Print"
+ },
+ {
+ "fieldname": "descriptive_test_templates",
+ "fieldtype": "Table",
+ "options": "Descriptive Test Template"
}
],
"links": [],
- "modified": "2020-03-25 16:53:01.740103",
+ "modified": "2020-07-13 12:57:09.925436",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Test Template",
diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
index 3521561..6f0d08c 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
@@ -14,37 +14,37 @@
create_item_from_template(self)
def validate(self):
+ if self.is_billable and (not self.lab_test_rate or self.lab_test_rate <= 0.0):
+ frappe.throw(_("Standard Selling Rate should be greater than zero."))
+ self.validate_conversion_factor()
self.enable_disable_item()
def on_update(self):
- # if change_in_item update Item and Price List
+ # If change_in_item update Item and Price List
if self.change_in_item and self.is_billable and self.item:
self.update_item()
item_price = self.item_price_exists()
if not item_price:
- if self.lab_test_rate != 0.0:
- price_list_name = frappe.db.get_value("Price List", {"selling": 1})
- if self.lab_test_rate:
- make_item_price(self.lab_test_code, price_list_name, self.lab_test_rate)
- else:
- make_item_price(self.lab_test_code, price_list_name, 0.0)
+ if self.lab_test_rate and self.lab_test_rate > 0.0:
+ price_list_name = frappe.db.get_value('Price List', {'selling': 1})
+ make_item_price(self.lab_test_code, price_list_name, self.lab_test_rate)
else:
- frappe.db.set_value("Item Price", item_price, "price_list_rate", self.lab_test_rate)
+ frappe.db.set_value('Item Price', item_price, 'price_list_rate', self.lab_test_rate)
- frappe.db.set_value(self.doctype, self.name, "change_in_item", 0)
+ self.db_set('change_in_item', 0)
elif not self.is_billable and self.item:
- frappe.db.set_value("Item", self.item, "disabled", 1)
+ frappe.db.set_value('Item', self.item, 'disabled', 1)
self.reload()
def on_trash(self):
- # remove template reference from item and disable item
+ # Remove template reference from item and disable item
if self.item:
try:
- frappe.delete_doc("Item", self.item)
+ frappe.delete_doc('Item', self.item)
except Exception:
- frappe.throw(_("Not permitted. Please disable the Lab Test Template"))
+ frappe.throw(_('Not permitted. Please disable the Lab Test Template'))
def enable_disable_item(self):
if self.is_billable:
@@ -54,78 +54,86 @@
frappe.db.set_value('Item', self.item, 'disabled', 0)
def update_item(self):
- item = frappe.get_doc("Item", self.item)
+ item = frappe.get_doc('Item', self.item)
if item:
item.update({
- "item_name": self.lab_test_name,
- "item_group": self.lab_test_group,
- "disabled": 0,
- "standard_rate": self.lab_test_rate,
- "description": self.lab_test_description
+ 'item_name': self.lab_test_name,
+ 'item_group': self.lab_test_group,
+ 'disabled': 0,
+ 'standard_rate': self.lab_test_rate,
+ 'description': self.lab_test_description
})
item.save()
def item_price_exists(self):
- item_price = frappe.db.exists({"doctype": "Item Price", "item_code": self.lab_test_code})
+ item_price = frappe.db.exists({'doctype': 'Item Price', 'item_code': self.lab_test_code})
if item_price:
return item_price[0][0]
else:
return False
+ def validate_conversion_factor(self):
+ if self.lab_test_template_type == "Single" and self.secondary_uom and not self.conversion_factor:
+ frappe.throw(_("Conversion Factor is mandatory"))
+ if self.lab_test_template_type == "Compound":
+ for item in self.normal_test_templates:
+ if item.secondary_uom and not item.conversion_factor:
+ frappe.throw(_("Conversion Factor is mandatory"))
+ if self.lab_test_template_type == "Grouped":
+ for group in self.lab_test_groups:
+ if group.template_or_new_line == "Add New Line" and group.secondary_uom and not group.conversion_factor:
+ frappe.throw(_("Conversion Factor is mandatory"))
+
def create_item_from_template(doc):
- disabled = doc.disabled
- if doc.is_billable and not doc.disabled:
- disabled = 0
-
uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom')
- # insert item
+ # Insert item
item = frappe.get_doc({
- "doctype": "Item",
- "item_code": doc.lab_test_code,
- "item_name":doc.lab_test_name,
- "item_group": doc.lab_test_group,
- "description":doc.lab_test_description,
- "is_sales_item": 1,
- "is_service_item": 1,
- "is_purchase_item": 0,
- "is_stock_item": 0,
- "show_in_website": 0,
- "is_pro_applicable": 0,
- "disabled": disabled,
- "stock_uom": uom
- }).insert(ignore_permissions=True, ignore_mandatory=True)
+ 'doctype': 'Item',
+ 'item_code': doc.lab_test_code,
+ 'item_name':doc.lab_test_name,
+ 'item_group': doc.lab_test_group,
+ 'description':doc.lab_test_description,
+ 'is_sales_item': 1,
+ 'is_service_item': 1,
+ 'is_purchase_item': 0,
+ 'is_stock_item': 0,
+ 'include_item_in_manufacturing': 0,
+ 'show_in_website': 0,
+ 'is_pro_applicable': 0,
+ 'disabled': 0 if doc.is_billable and not doc.disabled else doc.disabled,
+ 'stock_uom': uom
+ }).insert(ignore_permissions = True, ignore_mandatory = True)
- # insert item price
- # get item price list to insert item price
- if doc.lab_test_rate != 0.0:
- price_list_name = frappe.db.get_value("Price List", {"selling": 1})
+ # Insert item price
+ if doc.is_billable and doc.lab_test_rate != 0.0:
+ price_list_name = frappe.db.get_value('Price List', {'selling': 1})
if doc.lab_test_rate:
make_item_price(item.name, price_list_name, doc.lab_test_rate)
else:
make_item_price(item.name, price_list_name, 0.0)
# Set item in the template
- frappe.db.set_value("Lab Test Template", doc.name, "item", item.name)
+ frappe.db.set_value('Lab Test Template', doc.name, 'item', item.name)
doc.reload()
def make_item_price(item, price_list_name, item_price):
frappe.get_doc({
- "doctype": "Item Price",
- "price_list": price_list_name,
- "item_code": item,
- "price_list_rate": item_price
- }).insert(ignore_permissions=True, ignore_mandatory=True)
+ 'doctype': 'Item Price',
+ 'price_list': price_list_name,
+ 'item_code': item,
+ 'price_list_rate': item_price
+ }).insert(ignore_permissions = True, ignore_mandatory = True)
@frappe.whitelist()
def change_test_code_from_template(lab_test_code, doc):
doc = frappe._dict(json.loads(doc))
- if frappe.db.exists({ "doctype": "Item", "item_code": lab_test_code}):
- frappe.throw(_("Lab Test Item {0} already exist").format(lab_test_code))
+ if frappe.db.exists({'doctype': 'Item', 'item_code': lab_test_code}):
+ frappe.throw(_('Lab Test Item {0} already exist').format(lab_test_code))
else:
- rename_doc("Item", doc.name, lab_test_code, ignore_permissions=True)
- frappe.db.set_value("Lab Test Template", doc.name, "lab_test_code", lab_test_code)
- frappe.db.set_value("Lab Test Template", doc.name, "lab_test_name", lab_test_code)
- rename_doc("Lab Test Template", doc.name, lab_test_code, ignore_permissions=True)
- return lab_test_code
\ No newline at end of file
+ rename_doc('Item', doc.name, lab_test_code, ignore_permissions = True)
+ frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_code', lab_test_code)
+ frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_name', lab_test_code)
+ rename_doc('Lab Test Template', doc.name, lab_test_code, ignore_permissions = True)
+ return lab_test_code
diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js b/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js
index a86075f..a3417eb 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js
@@ -2,6 +2,6 @@
(c) ESS 2015-16
*/
frappe.listview_settings['Lab Test Template'] = {
- add_fields: ["lab_test_name", "lab_test_code", "lab_test_rate"],
- filters: [["disabled", "=", 0]]
+ add_fields: ['lab_test_name', 'lab_test_code', 'lab_test_rate'],
+ filters: [['disabled', '=', 0]]
};
diff --git a/erpnext/healthcare/doctype/medical_code/medical_code.json b/erpnext/healthcare/doctype/medical_code/medical_code.json
index a2e7247..5d69830 100644
--- a/erpnext/healthcare/doctype/medical_code/medical_code.json
+++ b/erpnext/healthcare/doctype/medical_code/medical_code.json
@@ -1,156 +1,69 @@
{
- "allow_copy": 1,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "beta": 1,
- "creation": "2017-06-21 13:02:56.122897",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_copy": 1,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "beta": 1,
+ "creation": "2017-06-21 13:02:56.122897",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "medical_code_standard",
+ "code",
+ "description"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "medical_code_standard",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Medical Code Standard",
- "length": 0,
- "no_copy": 0,
- "options": "Medical Code Standard",
- "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": "medical_code_standard",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "label": "Medical Code Standard",
+ "options": "Medical Code Standard",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "code",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "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": "code",
+ "fieldtype": "Data",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Code",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "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
+ "bold": 1,
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Description"
}
- ],
- "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-10-04 17:08:11.053418",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Medical Code",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "links": [],
+ "modified": "2020-06-29 14:02:30.980032",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Medical Code",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 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": "Physician",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Physician",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Healthcare",
- "search_fields": "code, description",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "restrict_to_domain": "Healthcare",
+ "search_fields": "code, description",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.js b/erpnext/healthcare/doctype/normal_test_items/normal_test_items.js
deleted file mode 100644
index 0371ddd..0000000
--- a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// Copyright (c) 2016, ESS
-// License: ESS license.txt
-
-
diff --git a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.json b/erpnext/healthcare/doctype/normal_test_items/normal_test_items.json
deleted file mode 100644
index a7a952b..0000000
--- a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.json
+++ /dev/null
@@ -1,301 +0,0 @@
-{
- "allow_copy": 1,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 1,
- "creation": "2016-02-22 15:06:08.295224",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "lab_test_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Test Name",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "lab_test_event",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Event",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.require_result_value == 1 ",
- "fieldname": "result_value",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Result Value",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "lab_test_uom",
- "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": "UOM",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "normal_range",
- "fieldtype": "Long Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Normal Range",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "lab_test_comment",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Comment",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fieldname": "require_result_value",
- "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": "Require Result Value",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "template",
- "fieldtype": "Link",
- "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": "Template",
- "length": 0,
- "no_copy": 0,
- "options": "Lab Test Template",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-09-04 11:42:43.095726",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Normal Test Items",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Healthcare",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.py b/erpnext/healthcare/doctype/normal_test_items/normal_test_items.py
deleted file mode 100644
index a0069d7..0000000
--- a/erpnext/healthcare/doctype/normal_test_items/normal_test_items.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, ESS and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-from frappe.model.document import Document
-
-class NormalTestItems(Document):
- pass
diff --git a/erpnext/healthcare/doctype/normal_test_items/__init__.py b/erpnext/healthcare/doctype/normal_test_result/__init__.py
similarity index 100%
rename from erpnext/healthcare/doctype/normal_test_items/__init__.py
rename to erpnext/healthcare/doctype/normal_test_result/__init__.py
diff --git a/erpnext/healthcare/doctype/normal_test_result/normal_test_result.json b/erpnext/healthcare/doctype/normal_test_result/normal_test_result.json
new file mode 100644
index 0000000..c8f43d3
--- /dev/null
+++ b/erpnext/healthcare/doctype/normal_test_result/normal_test_result.json
@@ -0,0 +1,186 @@
+{
+ "actions": [],
+ "allow_copy": 1,
+ "beta": 1,
+ "creation": "2016-02-22 15:06:08.295224",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "lab_test_name",
+ "lab_test_event",
+ "result_value",
+ "lab_test_uom",
+ "secondary_uom_result",
+ "secondary_uom",
+ "conversion_factor",
+ "column_break_10",
+ "allow_blank",
+ "normal_range",
+ "lab_test_comment",
+ "bold",
+ "italic",
+ "underline",
+ "template",
+ "require_result_value"
+ ],
+ "fields": [
+ {
+ "fieldname": "lab_test_name",
+ "fieldtype": "Data",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Test Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "lab_test_event",
+ "fieldtype": "Data",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Event",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.require_result_value",
+ "fieldname": "result_value",
+ "fieldtype": "Data",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Result Value"
+ },
+ {
+ "depends_on": "eval:doc.require_result_value",
+ "fieldname": "lab_test_uom",
+ "fieldtype": "Link",
+ "label": "UOM",
+ "options": "Lab Test UOM",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.require_result_value",
+ "fieldname": "normal_range",
+ "fieldtype": "Long Text",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Normal Range",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.require_result_value",
+ "fieldname": "lab_test_comment",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "in_list_view": 1,
+ "label": "Comment",
+ "no_copy": 1,
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "fieldname": "template",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Template",
+ "options": "Lab Test Template",
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "depends_on": "eval:doc.require_result_value",
+ "fieldname": "secondary_uom",
+ "fieldtype": "Link",
+ "label": "Secondary UOM",
+ "options": "Lab Test UOM",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "depends_on": "secondary_uom",
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "label": "Conversion Factor",
+ "mandatory_depends_on": "secondary_uom",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.require_result_value && doc.result_value",
+ "fieldname": "secondary_uom_result",
+ "fieldtype": "Data",
+ "label": "Secondary UOM Result",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "0",
+ "depends_on": "eval:doc.require_result_value",
+ "fieldname": "bold",
+ "fieldtype": "Check",
+ "label": "Bold",
+ "no_copy": 1,
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "0",
+ "depends_on": "eval:doc.require_result_value",
+ "fieldname": "italic",
+ "fieldtype": "Check",
+ "label": "Italic",
+ "no_copy": 1,
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "0",
+ "depends_on": "eval:doc.require_result_value",
+ "fieldname": "underline",
+ "fieldtype": "Check",
+ "label": "Underline",
+ "no_copy": 1,
+ "print_hide": 1,
+ "report_hide": 1
+ },
+ {
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "require_result_value",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Require Result Value",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1
+ },
+ {
+ "default": "1",
+ "depends_on": "eval:doc.require_result_value",
+ "fieldname": "allow_blank",
+ "fieldtype": "Check",
+ "label": "Allow Blank",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-07-08 16:03:17.522893",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Normal Test Result",
+ "owner": "Administrator",
+ "permissions": [],
+ "restrict_to_domain": "Healthcare",
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.py b/erpnext/healthcare/doctype/normal_test_result/normal_test_result.py
similarity index 85%
rename from erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.py
rename to erpnext/healthcare/doctype/normal_test_result/normal_test_result.py
index c67531c..63abf02 100644
--- a/erpnext/healthcare/doctype/lab_test_groups/lab_test_groups.py
+++ b/erpnext/healthcare/doctype/normal_test_result/normal_test_result.py
@@ -5,5 +5,5 @@
from __future__ import unicode_literals
from frappe.model.document import Document
-class LabTestGroups(Document):
+class NormalTestResult(Document):
pass
diff --git a/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json b/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json
index a36c28d..8dd6476 100644
--- a/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json
+++ b/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json
@@ -1,202 +1,84 @@
{
- "allow_copy": 1,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 1,
- "creation": "2016-02-22 16:09:54.310628",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
+ "actions": [],
+ "allow_copy": 1,
+ "beta": 1,
+ "creation": "2016-02-22 16:09:54.310628",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "heading_text",
+ "lab_test_event",
+ "allow_blank",
+ "lab_test_uom",
+ "secondary_uom",
+ "conversion_factor",
+ "column_break_5",
+ "normal_range"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "heading_text",
- "fieldtype": "Heading",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Test",
- "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": "heading_text",
+ "fieldtype": "Heading",
+ "ignore_xss_filter": 1,
+ "label": "Test"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "lab_test_event",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Event",
- "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": "lab_test_event",
+ "fieldtype": "Data",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Event"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "lab_test_uom",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "UOM",
- "length": 0,
- "no_copy": 0,
- "options": "Lab Test UOM",
- "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": "lab_test_uom",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "in_list_view": 1,
+ "label": "UOM",
+ "options": "Lab Test UOM"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "normal_range",
- "fieldtype": "Long Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Normal Range",
- "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": "normal_range",
+ "fieldtype": "Long Text",
+ "ignore_xss_filter": 1,
+ "in_list_view": 1,
+ "label": "Normal Range"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_5",
- "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_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "secondary_uom",
+ "fieldtype": "Link",
+ "label": "Secondary UOM",
+ "options": "Lab Test UOM"
+ },
+ {
+ "depends_on": "secondary_uom",
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "label": "Conversion Factor",
+ "mandatory_depends_on": "secondary_uom"
+ },
+ {
+ "default": "0",
+ "fieldname": "allow_blank",
+ "fieldtype": "Check",
+ "label": "Allow Blank"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-09-04 11:42:30.766950",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Normal Test Template",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Healthcare",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-06-23 13:28:40.156224",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Normal Test Template",
+ "owner": "Administrator",
+ "permissions": [],
+ "restrict_to_domain": "Healthcare",
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/lab_test_groups/__init__.py b/erpnext/healthcare/doctype/organism/__init__.py
similarity index 100%
copy from erpnext/healthcare/doctype/lab_test_groups/__init__.py
copy to erpnext/healthcare/doctype/organism/__init__.py
diff --git a/erpnext/healthcare/doctype/organism/organism.js b/erpnext/healthcare/doctype/organism/organism.js
new file mode 100644
index 0000000..fbcb094
--- /dev/null
+++ b/erpnext/healthcare/doctype/organism/organism.js
@@ -0,0 +1,5 @@
+// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Organism', {
+});
diff --git a/erpnext/healthcare/doctype/organism/organism.json b/erpnext/healthcare/doctype/organism/organism.json
new file mode 100644
index 0000000..88a7686
--- /dev/null
+++ b/erpnext/healthcare/doctype/organism/organism.json
@@ -0,0 +1,152 @@
+{
+ "allow_copy": 0,
+ "allow_events_in_timeline": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "field:organism",
+ "beta": 1,
+ "creation": "2019-09-06 16:29:07.797960",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "organism",
+ "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": "Organism",
+ "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": 1
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "abbr",
+ "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": "Abbr",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 1
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2019-10-04 19:45:33.353753",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Organism",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ },
+ {
+ "amend": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Healthcare Administrator",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Healthcare",
+ "search_fields": "organism, abbr",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "organism",
+ "track_changes": 0,
+ "track_seen": 0,
+ "track_views": 0
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py b/erpnext/healthcare/doctype/organism/organism.py
similarity index 63%
copy from erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py
copy to erpnext/healthcare/doctype/organism/organism.py
index 35c8efd..1ead762 100644
--- a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py
+++ b/erpnext/healthcare/doctype/organism/organism.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2015, ESS and contributors
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
-class SensitivityTestItems(Document):
+class Organism(Document):
pass
diff --git a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js b/erpnext/healthcare/doctype/organism/test_organism.js
similarity index 68%
rename from erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js
rename to erpnext/healthcare/doctype/organism/test_organism.js
index 7633815..d57e553 100644
--- a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.js
+++ b/erpnext/healthcare/doctype/organism/test_organism.js
@@ -2,15 +2,15 @@
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
-QUnit.test("test: POS Closing Voucher", function (assert) {
+QUnit.test("test: Organism", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
- // insert a new POS Closing Voucher
- () => frappe.tests.make('POS Closing Voucher', [
+ // insert a new Organism
+ () => frappe.tests.make('Organism', [
// values to be set
{key: 'value'}
]),
diff --git a/erpnext/healthcare/doctype/organism/test_organism.py b/erpnext/healthcare/doctype/organism/test_organism.py
new file mode 100644
index 0000000..ecb9665
--- /dev/null
+++ b/erpnext/healthcare/doctype/organism/test_organism.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+import unittest
+
+class TestOrganism(unittest.TestCase):
+ pass
diff --git a/erpnext/healthcare/doctype/lab_test_groups/__init__.py b/erpnext/healthcare/doctype/organism_test_item/__init__.py
similarity index 100%
copy from erpnext/healthcare/doctype/lab_test_groups/__init__.py
copy to erpnext/healthcare/doctype/organism_test_item/__init__.py
diff --git a/erpnext/healthcare/doctype/organism_test_item/organism_test_item.json b/erpnext/healthcare/doctype/organism_test_item/organism_test_item.json
new file mode 100644
index 0000000..56d0a4d
--- /dev/null
+++ b/erpnext/healthcare/doctype/organism_test_item/organism_test_item.json
@@ -0,0 +1,144 @@
+{
+ "allow_copy": 0,
+ "allow_events_in_timeline": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 1,
+ "creation": "2019-09-06 16:37:59.698996",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "organism",
+ "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": "Organism",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Organism",
+ "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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "colony_population",
+ "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": "Colony Population",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "colony_uom",
+ "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": "Colony UOM",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Lab Test UOM",
+ "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
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2019-10-04 19:48:04.104234",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Organism Test Item",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 0,
+ "track_seen": 0,
+ "track_views": 0
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py b/erpnext/healthcare/doctype/organism_test_item/organism_test_item.py
similarity index 61%
copy from erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py
copy to erpnext/healthcare/doctype/organism_test_item/organism_test_item.py
index 35c8efd..019a55b 100644
--- a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py
+++ b/erpnext/healthcare/doctype/organism_test_item/organism_test_item.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2015, ESS and contributors
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
-class SensitivityTestItems(Document):
+class OrganismTestItem(Document):
pass
diff --git a/erpnext/healthcare/doctype/lab_test_groups/__init__.py b/erpnext/healthcare/doctype/organism_test_result/__init__.py
similarity index 100%
copy from erpnext/healthcare/doctype/lab_test_groups/__init__.py
copy to erpnext/healthcare/doctype/organism_test_result/__init__.py
diff --git a/erpnext/healthcare/doctype/organism_test_result/organism_test_result.json b/erpnext/healthcare/doctype/organism_test_result/organism_test_result.json
new file mode 100644
index 0000000..8b238de
--- /dev/null
+++ b/erpnext/healthcare/doctype/organism_test_result/organism_test_result.json
@@ -0,0 +1,144 @@
+{
+ "allow_copy": 0,
+ "allow_events_in_timeline": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 1,
+ "creation": "2019-09-06 16:37:59.698996",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "organism",
+ "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": "Organism",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Organism",
+ "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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "colony_population",
+ "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": "Colony Population",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fetch_if_empty": 0,
+ "fieldname": "colony_uom",
+ "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": "Colony UOM",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Lab Test UOM",
+ "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
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2019-10-04 19:48:04.104234",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Organism Test Result",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 0,
+ "track_seen": 0,
+ "track_views": 0
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py b/erpnext/healthcare/doctype/organism_test_result/organism_test_result.py
similarity index 61%
copy from erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py
copy to erpnext/healthcare/doctype/organism_test_result/organism_test_result.py
index 87ce842..02393c2 100644
--- a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py
+++ b/erpnext/healthcare/doctype/organism_test_result/organism_test_result.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2018, 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
from frappe.model.document import Document
-class POSClosingVoucherTaxes(Document):
+class OrganismTestResult(Document):
pass
diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py
index 30a1e45..63dd8d4 100644
--- a/erpnext/healthcare/doctype/patient/patient.py
+++ b/erpnext/healthcare/doctype/patient/patient.py
@@ -172,3 +172,15 @@
if vital_sign:
details.update(vital_sign[0])
return details
+
+def get_timeline_data(doctype, name):
+ """Return timeline data from medical records"""
+ return dict(frappe.db.sql('''
+ SELECT
+ unix_timestamp(communication_date), count(*)
+ FROM
+ `tabPatient Medical Record`
+ WHERE
+ patient=%s
+ and `communication_date` > date_sub(curdate(), interval 1 year)
+ GROUP BY communication_date''', name))
diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json
index 15c9434..eb0021f 100644
--- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json
+++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json
@@ -63,7 +63,8 @@
{
"fieldname": "assessment_datetime",
"fieldtype": "Datetime",
- "label": "Assessment Datetime"
+ "label": "Assessment Datetime",
+ "reqd": 1
},
{
"fieldname": "section_break_7",
@@ -139,7 +140,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-05-25 14:38:38.302399",
+ "modified": "2020-06-25 00:25:13.208400",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Assessment",
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
index 56401a3..262fc46 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
@@ -73,7 +73,7 @@
insert_encounter_to_medical_record(encounter)
def delete_medical_record(encounter):
- frappe.db.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name)
+ frappe.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name)
def set_subject_field(encounter):
subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
diff --git a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.json b/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.json
deleted file mode 100644
index 86f5e26..0000000
--- a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.json
+++ /dev/null
@@ -1,103 +0,0 @@
-{
- "allow_copy": 1,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 1,
- "creation": "2016-02-22 15:18:01.769903",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "antibiotic",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Antibiotic",
- "length": 0,
- "no_copy": 0,
- "options": "Antibiotic",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "antibiotic_sensitivity",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Sensitivity",
- "length": 0,
- "no_copy": 0,
- "options": "Sensitivity",
- "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
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-10-05 11:08:06.327972",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Sensitivity Test Items",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Healthcare",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/sensitivity_test_items/__init__.py b/erpnext/healthcare/doctype/sensitivity_test_result/__init__.py
similarity index 100%
rename from erpnext/healthcare/doctype/sensitivity_test_items/__init__.py
rename to erpnext/healthcare/doctype/sensitivity_test_result/__init__.py
diff --git a/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json b/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json
new file mode 100644
index 0000000..768c177
--- /dev/null
+++ b/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json
@@ -0,0 +1,103 @@
+{
+ "allow_copy": 1,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 1,
+ "creation": "2016-02-22 15:18:01.769903",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "antibiotic",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 1,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Antibiotic",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Antibiotic",
+ "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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "antibiotic_sensitivity",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 1,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Sensitivity",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Sensitivity",
+ "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
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2017-10-05 11:08:06.327972",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Sensitivity Test Result",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "restrict_to_domain": "Healthcare",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 0,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py b/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py
similarity index 83%
rename from erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py
rename to erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py
index 35c8efd..64f1e6c 100644
--- a/erpnext/healthcare/doctype/sensitivity_test_items/sensitivity_test_items.py
+++ b/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py
@@ -5,5 +5,5 @@
from __future__ import unicode_literals
from frappe.model.document import Document
-class SensitivityTestItems(Document):
+class SensitivityTestResult(Document):
pass
diff --git a/erpnext/healthcare/doctype/special_test_items/__init__.py b/erpnext/healthcare/doctype/special_test_items/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/healthcare/doctype/special_test_items/__init__.py
+++ /dev/null
diff --git a/erpnext/healthcare/doctype/special_test_items/special_test_items.json b/erpnext/healthcare/doctype/special_test_items/special_test_items.json
deleted file mode 100644
index a15806e..0000000
--- a/erpnext/healthcare/doctype/special_test_items/special_test_items.json
+++ /dev/null
@@ -1,175 +0,0 @@
-{
- "allow_copy": 1,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 1,
- "creation": "2016-02-22 15:12:36.202380",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "lab_test_particulars",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Particulars",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.require_result_value == 1",
- "fieldname": "result_value",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Value",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
- "width": ""
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "require_result_value",
- "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": "Require Result Value",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "template",
- "fieldtype": "Link",
- "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": "Template",
- "length": 0,
- "no_copy": 0,
- "options": "Lab Test Template",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-09-04 12:01:18.801216",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Special Test Items",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Healthcare",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/special_test_items/special_test_items.py b/erpnext/healthcare/doctype/special_test_items/special_test_items.py
deleted file mode 100644
index 17080b7..0000000
--- a/erpnext/healthcare/doctype/special_test_items/special_test_items.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, ESS and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-from frappe.model.document import Document
-
-class SpecialTestItems(Document):
- pass
diff --git a/erpnext/healthcare/doctype/special_test_template/special_test_template.json b/erpnext/healthcare/doctype/special_test_template/special_test_template.json
deleted file mode 100644
index 372af0a..0000000
--- a/erpnext/healthcare/doctype/special_test_template/special_test_template.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
- "allow_copy": 1,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 1,
- "creation": "2016-02-22 16:12:12.394200",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "particulars",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 1,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Result Component",
- "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,
- "unique": 0
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-10-04 16:20:09.565316",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Special Test Template",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Healthcare",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
index c19be17..e0f015f 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
@@ -5,6 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
+from frappe.utils import today
class TherapyPlan(Document):
def validate(self):
@@ -45,4 +46,6 @@
therapy_session.rate = therapy_type.rate
therapy_session.exercises = therapy_type.exercises
+ if frappe.flags.in_test:
+ therapy_session.start_date = today()
return therapy_session.as_dict()
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.json b/erpnext/healthcare/doctype/therapy_session/therapy_session.json
index 00d74a0..dc0cafc 100644
--- a/erpnext/healthcare/doctype/therapy_session/therapy_session.json
+++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.json
@@ -19,6 +19,7 @@
"practitioner",
"department",
"details_section",
+ "medical_code",
"duration",
"rate",
"location",
@@ -153,7 +154,8 @@
{
"fieldname": "start_date",
"fieldtype": "Date",
- "label": "Start Date"
+ "label": "Start Date",
+ "reqd": 1
},
{
"fieldname": "start_time",
@@ -206,11 +208,19 @@
"fieldtype": "Data",
"label": "Patient Name",
"read_only": 1
+ },
+ {
+ "fetch_from": "therapy_type.medical_code",
+ "fieldname": "medical_code",
+ "fieldtype": "Link",
+ "label": "Medical Code",
+ "options": "Medical Code",
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-29 16:49:16.286006",
+ "modified": "2020-06-30 10:56:10.354268",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Session",
diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.js b/erpnext/healthcare/doctype/therapy_type/therapy_type.js
index 7a61b0d..6e155dc 100644
--- a/erpnext/healthcare/doctype/therapy_type/therapy_type.js
+++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.js
@@ -45,6 +45,16 @@
medical_department: function(frm) {
mark_change_in_item(frm);
+ },
+
+ medical_code: function(frm) {
+ frm.set_query("medical_code", function() {
+ return {
+ filters: {
+ medical_code_standard: frm.doc.medical_code_standard
+ }
+ };
+ });
}
});
diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.json b/erpnext/healthcare/doctype/therapy_type/therapy_type.json
index 0b3c3ca..f365b1d 100644
--- a/erpnext/healthcare/doctype/therapy_type/therapy_type.json
+++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.json
@@ -22,6 +22,9 @@
"item_group",
"column_break_12",
"description",
+ "medical_coding_section",
+ "medical_code_standard",
+ "medical_code",
"section_break_18",
"therapy_for",
"add_exercises",
@@ -160,10 +163,30 @@
{
"fieldname": "section_break_18",
"fieldtype": "Section Break"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "medical_coding_section",
+ "fieldtype": "Section Break",
+ "label": "Medical Coding",
+ "options": "Medical Coding"
+ },
+ {
+ "fieldname": "medical_code_standard",
+ "fieldtype": "Link",
+ "label": "Medical Code Standard",
+ "options": "Medical Code Standard"
+ },
+ {
+ "depends_on": "medical_code_standard",
+ "fieldname": "medical_code",
+ "fieldtype": "Link",
+ "label": "Medical Code",
+ "options": "Medical Code"
}
],
"links": [],
- "modified": "2020-04-21 13:09:04.006289",
+ "modified": "2020-06-29 14:18:50.669951",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Type",
diff --git a/erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json b/erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json
new file mode 100644
index 0000000..2fea668
--- /dev/null
+++ b/erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json
@@ -0,0 +1,62 @@
+{
+ "cards": [
+ {
+ "card": "Total Patients"
+ },
+ {
+ "card": "Total Patients Admitted"
+ },
+ {
+ "card": "Open Appointments"
+ },
+ {
+ "card": "Appointments to Bill"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "Patient Appointments",
+ "width": "Full"
+ },
+ {
+ "chart": "In-Patient Status",
+ "width": "Half"
+ },
+ {
+ "chart": "Clinical Procedures Status",
+ "width": "Half"
+ },
+ {
+ "chart": "Lab Tests",
+ "width": "Half"
+ },
+ {
+ "chart": "Clinical Procedures",
+ "width": "Half"
+ },
+ {
+ "chart": "Symptoms",
+ "width": "Half"
+ },
+ {
+ "chart": "Diagnoses",
+ "width": "Half"
+ },
+ {
+ "chart": "Department wise Patient Appointments",
+ "width": "Full"
+ }
+ ],
+ "creation": "2020-07-14 18:17:54.823311",
+ "dashboard_name": "Healthcare",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 0,
+ "is_standard": 1,
+ "modified": "2020-07-22 15:36:34.220387",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Healthcare",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
index 3e50726..56c3c13 100644
--- a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
+++ b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
@@ -10,7 +10,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/healthcare",
"idx": 0,
"is_complete": 0,
- "modified": "2020-05-26 23:16:37.603361",
+ "modified": "2020-07-08 14:06:19.512946",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare",
@@ -35,8 +35,7 @@
"step": "Explore Clinical Procedure Templates"
}
],
- "subtitle": "Patients, Practitioner Schedules, Settings and more.",
- "success_message": "Yayy! The Healthcare Module is all set up!",
- "title": "Let's Setup the Healthcare Module",
- "user_can_dismiss": 1
+ "subtitle": "Patients, Practitioner Schedules, Settings, and more.",
+ "success_message": "The Healthcare Module is all set up!",
+ "title": "Let's Set Up the Healthcare Module."
}
\ No newline at end of file
diff --git a/erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json b/erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json
new file mode 100644
index 0000000..3e4d4e2
--- /dev/null
+++ b/erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-14 18:17:54.792773",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Patient Appointment",
+ "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Patient Appointment\",\"invoiced\",\"=\",0,false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Appointments To Bill",
+ "modified": "2020-07-22 13:27:58.038577",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Appointments to Bill",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/number_card/open_appointments/open_appointments.json b/erpnext/healthcare/number_card/open_appointments/open_appointments.json
new file mode 100644
index 0000000..8d121cc
--- /dev/null
+++ b/erpnext/healthcare/number_card/open_appointments/open_appointments.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-14 18:17:54.771092",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Patient Appointment",
+ "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Patient Appointment\",\"status\",\"=\",\"Open\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Open Appointments",
+ "modified": "2020-07-22 13:27:09.542122",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Open Appointments",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/number_card/total_patients/total_patients.json b/erpnext/healthcare/number_card/total_patients/total_patients.json
new file mode 100644
index 0000000..75441a6
--- /dev/null
+++ b/erpnext/healthcare/number_card/total_patients/total_patients.json
@@ -0,0 +1,20 @@
+{
+ "creation": "2020-07-14 18:17:54.727946",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Patient",
+ "filters_json": "[[\"Patient\",\"status\",\"=\",\"Active\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Patients",
+ "modified": "2020-07-22 13:26:02.643534",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Total Patients",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json b/erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json
new file mode 100644
index 0000000..69a967d
--- /dev/null
+++ b/erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json
@@ -0,0 +1,20 @@
+{
+ "creation": "2020-07-14 18:17:54.749754",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Patient",
+ "filters_json": "[[\"Patient\",\"inpatient_status\",\"=\",\"Admitted\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Patients Admitted",
+ "modified": "2020-07-22 13:26:20.027788",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Total Patients Admitted",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/lab_test_groups/__init__.py b/erpnext/healthcare/page/patient_progress/__init__.py
similarity index 100%
copy from erpnext/healthcare/doctype/lab_test_groups/__init__.py
copy to erpnext/healthcare/page/patient_progress/__init__.py
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.css b/erpnext/healthcare/page/patient_progress/patient_progress.css
new file mode 100644
index 0000000..5d85a74
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.css
@@ -0,0 +1,165 @@
+/* sidebar */
+
+.layout-side-section .frappe-control[data-fieldname='patient'] {
+ max-width: 300px;
+}
+
+.patient-image-container {
+ margin-top: 17px;
+}
+
+.patient-image {
+ display: inline-block;
+ width: 100%;
+ height: 0;
+ padding: 50% 0px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center center;
+ border-radius: 4px;
+}
+
+.patient-details {
+ margin: -5px 5px;
+}
+
+.important-links {
+ margin: 30px 5px;
+}
+
+.patient-name {
+ font-size: 20px;
+}
+
+/* heatmap */
+
+.heatmap-container {
+ height: 170px;
+}
+
+.patient-heatmap {
+ width: 80%;
+ display: inline-block;
+}
+
+.patient-heatmap .chart-container {
+ margin-left: 30px;
+}
+
+.patient-heatmap .frappe-chart {
+ margin-top: 5px;
+}
+
+.patient-heatmap .frappe-chart .chart-legend {
+ display: none;
+}
+
+.heatmap-container .chart-filter {
+ position: relative;
+ top: 5px;
+ margin-right: 10px;
+}
+
+/* percentage chart */
+
+.percentage-chart-container {
+ height: 130px;
+}
+
+.percentage-chart-container .chart-filter {
+ position: relative;
+ top: 5px;
+ margin-right: 10px;
+}
+
+.therapy-session-percentage-chart .frappe-chart {
+ position: absolute;
+ top: 5px;
+}
+
+/* line charts */
+
+.date-field .clearfix {
+ display: none;
+}
+
+.date-field .help-box {
+ display: none;
+}
+
+.date-field .frappe-control {
+ margin-bottom: 0px !important;
+}
+
+.date-field .form-group {
+ margin-bottom: 0px !important;
+}
+
+/* common */
+
+text.title {
+ text-transform: uppercase;
+ font-size: 11px;
+ margin-left: 20px;
+ margin-top: 20px;
+ display: block;
+}
+
+.chart-filter-search {
+ margin-left: 35px;
+ width: 25%;
+}
+
+.chart-column-container {
+ border-bottom: 1px solid #d1d8dd;
+ margin: 5px 0;
+}
+
+.line-chart-container .frappe-chart {
+ margin-top: -20px;
+}
+
+.line-chart-container {
+ margin-bottom: 20px;
+}
+
+.chart-control {
+ align-self: center;
+ display: flex;
+ flex-direction: row-reverse;
+ margin-top: -25px;
+}
+
+.chart-control > * {
+ margin-right: 10px;
+}
+
+/* mobile */
+
+@media (max-width: 991px) {
+ .patient-progress-sidebar {
+ display: flex;
+ }
+
+ .percentage-chart-container {
+ border-top: 1px solid #d1d8dd;
+ }
+
+ .percentage-chart-container .chart-filter {
+ position: relative;
+ top: 12px;
+ margin-right: 10px;
+ }
+
+ .patient-progress-sidebar .important-links {
+ margin: 0;
+ }
+
+ .patient-progress-sidebar .patient-details {
+ width: 50%;
+ }
+
+ .chart-filter-search {
+ width: 40%;
+ }
+}
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.html b/erpnext/healthcare/page/patient_progress/patient_progress.html
new file mode 100644
index 0000000..c20537e
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.html
@@ -0,0 +1,68 @@
+<div class="row patient-progress">
+ <div class="col-md-12">
+ <div class="progress-graphs">
+ <div class="chart-column-container heatmap-container hidden-xs hidden-sm">
+ <div class="patient-heatmap"></div>
+ </div>
+ <div class="chart-column-container percentage-chart-container">
+ <div class="therapy-session-percentage-chart"></div>
+ </div>
+
+ <div class="therapy-progress">
+ <div class="chart-head">
+ <text class="title" text-anchor="start">Therapy Progress</text>
+ <div class="chart-control pull-right"></div>
+ </div>
+ <div class="row">
+ <div class="chart-filter-search therapy-type-search"></div>
+ </div>
+ <div class="col-md-12 chart-column-container line-chart-container">
+ <div class="therapy-progress-line-chart">
+ </div>
+ </div>
+ </div>
+
+ <div class="assessment-results">
+ <div class="chart-head">
+ <text class="title" text-anchor="start">Assessment Results</text>
+ <div class="chart-control pull-right"></div>
+ </div>
+ <div class="row">
+ <div class="chart-filter-search assessment-template-search"></div>
+ </div>
+ <div class="col-md-12 chart-column-container line-chart-container">
+ <div class="assessment-results-line-chart">
+ </div>
+ </div>
+ </div>
+
+ <div class="therapy-assessment-correlation progress-line-chart">
+ <div class="chart-head">
+ <text class="title" text-anchor="start">Therapy Type and Assessment Correlation</text>
+ <div class="chart-control pull-right"></div>
+ </div>
+ <div class="row">
+ <div class="chart-filter-search assessment-correlation-template-search"></div>
+ </div>
+ <div class="col-md-12 chart-column-container line-chart-container">
+ <div class="therapy-assessment-correlation-chart">
+ </div>
+ </div>
+ </div>
+
+ <div class="assessment-parameter-progress progress-line-chart">
+ <div class="chart-head">
+ <text class="title" text-anchor="start">Assessment Parameter Wise Progress</text>
+ <div class="chart-control pull-right"></div>
+ </div>
+ <div class="row">
+ <div class="chart-filter-search assessment-parameter-search"></div>
+ </div>
+ <div class="col-md-12 line-chart-container">
+ <div class="assessment-parameter-progress-chart">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.js b/erpnext/healthcare/page/patient_progress/patient_progress.js
new file mode 100644
index 0000000..2410b0c
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.js
@@ -0,0 +1,531 @@
+frappe.pages['patient-progress'].on_page_load = function(wrapper) {
+
+ frappe.ui.make_app_page({
+ parent: wrapper,
+ title: __('Patient Progress')
+ });
+
+ let patient_progress = new PatientProgress(wrapper);
+ $(wrapper).bind('show', ()=> {
+ patient_progress.show();
+ });
+};
+
+class PatientProgress {
+
+ constructor(wrapper) {
+ this.wrapper = $(wrapper);
+ this.page = wrapper.page;
+ this.sidebar = this.wrapper.find('.layout-side-section');
+ this.main_section = this.wrapper.find('.layout-main-section');
+ }
+
+ show() {
+ frappe.breadcrumbs.add('Healthcare');
+ this.sidebar.empty();
+
+ let me = this;
+ let patient = frappe.ui.form.make_control({
+ parent: me.sidebar,
+ df: {
+ fieldtype: 'Link',
+ options: 'Patient',
+ fieldname: 'patient',
+ placeholder: __('Select Patient'),
+ only_select: true,
+ change: () => {
+ me.patient_id = '';
+ if (me.patient_id != patient.get_value() && patient.get_value()) {
+ me.start = 0;
+ me.patient_id = patient.get_value();
+ me.make_patient_profile();
+ }
+ }
+ }
+ });
+ patient.refresh();
+
+ if (frappe.route_options && !this.patient) {
+ patient.set_value(frappe.route_options.patient);
+ this.patient_id = frappe.route_options.patient;
+ }
+
+ this.sidebar.find('[data-fieldname="patient"]').append('<div class="patient-info"></div>');
+ }
+
+ make_patient_profile() {
+ this.page.set_title(__('Patient Progress'));
+ this.main_section.empty().append(frappe.render_template('patient_progress'));
+ this.render_patient_details();
+ this.render_heatmap();
+ this.render_percentage_chart('therapy_type', 'Therapy Type Distribution');
+ this.create_percentage_chart_filters();
+ this.show_therapy_progress();
+ this.show_assessment_results();
+ this.show_therapy_assessment_correlation();
+ this.show_assessment_parameter_progress();
+ }
+
+ get_patient_info() {
+ return frappe.xcall('frappe.client.get', {
+ doctype: 'Patient',
+ name: this.patient_id
+ }).then((patient) => {
+ if (patient) {
+ this.patient = patient;
+ }
+ });
+ }
+
+ get_therapy_sessions_count() {
+ return frappe.xcall(
+ 'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_count', {
+ patient: this.patient_id,
+ }
+ ).then(data => {
+ if (data) {
+ this.total_therapy_sessions = data.total_therapy_sessions;
+ this.therapy_sessions_this_month = data.therapy_sessions_this_month;
+ }
+ });
+ }
+
+ render_patient_details() {
+ this.get_patient_info().then(() => {
+ this.get_therapy_sessions_count().then(() => {
+ $('.patient-info').empty().append(frappe.render_template('patient_progress_sidebar', {
+ patient_image: this.patient.image,
+ patient_name: this.patient.patient_name,
+ patient_gender: this.patient.sex,
+ patient_mobile: this.patient.mobile,
+ total_therapy_sessions: this.total_therapy_sessions,
+ therapy_sessions_this_month: this.therapy_sessions_this_month
+ }));
+
+ this.setup_patient_profile_links();
+ });
+ });
+ }
+
+ setup_patient_profile_links() {
+ this.wrapper.find('.patient-profile-link').on('click', () => {
+ frappe.set_route('Form', 'Patient', this.patient_id);
+ });
+
+ this.wrapper.find('.therapy-plan-link').on('click', () => {
+ frappe.route_options = {
+ 'patient': this.patient_id,
+ 'docstatus': 1
+ };
+ frappe.set_route('List', 'Therapy Plan');
+ });
+
+ this.wrapper.find('.patient-history').on('click', () => {
+ frappe.route_options = {
+ 'patient': this.patient_id
+ };
+ frappe.set_route('patient_history');
+ });
+ }
+
+ render_heatmap() {
+ this.heatmap = new frappe.Chart('.patient-heatmap', {
+ type: 'heatmap',
+ countLabel: 'Interactions',
+ data: {},
+ discreteDomains: 0
+ });
+ this.update_heatmap_data();
+ this.create_heatmap_chart_filters();
+ }
+
+ update_heatmap_data(date_from) {
+ frappe.xcall('erpnext.healthcare.page.patient_progress.patient_progress.get_patient_heatmap_data', {
+ patient: this.patient_id,
+ date: date_from || frappe.datetime.year_start(),
+ }).then((data) => {
+ this.heatmap.update( {dataPoints: data} );
+ });
+ }
+
+ create_heatmap_chart_filters() {
+ this.get_patient_info().then(() => {
+ let filters = [
+ {
+ label: frappe.dashboard_utils.get_year(frappe.datetime.now_date()),
+ options: frappe.dashboard_utils.get_years_since_creation(this.patient.creation),
+ action: (selected_item) => {
+ this.update_heatmap_data(frappe.datetime.obj_to_str(selected_item));
+ }
+ },
+ ];
+ frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.heatmap-container');
+ });
+ }
+
+ render_percentage_chart(field, title) {
+ frappe.xcall(
+ 'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_distribution_data', {
+ patient: this.patient_id,
+ field: field
+ }
+ ).then(chart => {
+ if (chart.labels.length) {
+ this.percentage_chart = new frappe.Chart('.therapy-session-percentage-chart', {
+ title: title,
+ type: 'percentage',
+ data: {
+ labels: chart.labels,
+ datasets: chart.datasets
+ },
+ truncateLegends: 1,
+ barOptions: {
+ height: 11,
+ depth: 1
+ },
+ height: 160,
+ maxSlices: 8,
+ colors: ['#5e64ff', '#743ee2', '#ff5858', '#ffa00a', '#feef72', '#28a745', '#98d85b', '#a9a7ac'],
+ });
+ } else {
+ this.wrapper.find('.percentage-chart-container').hide();
+ }
+ });
+ }
+
+ create_percentage_chart_filters() {
+ let filters = [
+ {
+ label: 'Therapy Type',
+ options: ['Therapy Type', 'Exercise Type'],
+ fieldnames: ['therapy_type', 'exercise_type'],
+ action: (selected_item, fieldname) => {
+ let title = selected_item + ' Distribution';
+ this.render_percentage_chart(fieldname, title);
+ }
+ },
+ ];
+ frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.percentage-chart-container');
+ }
+
+ create_time_span_filters(action_method, parent) {
+ let chart_control = $(parent).find('.chart-control');
+ let filters = [
+ {
+ label: 'Last Month',
+ options: ['Select Date Range', 'Last Week', 'Last Month', 'Last Quarter', 'Last Year'],
+ action: (selected_item) => {
+ if (selected_item === 'Select Date Range') {
+ this.render_date_range_fields(action_method, chart_control);
+ } else {
+ // hide date range field if visible
+ let date_field = $(parent).find('.date-field');
+ if (date_field.is(':visible')) {
+ date_field.hide();
+ }
+ this[action_method](selected_item);
+ }
+ }
+ }
+ ];
+ frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', chart_control, 1);
+ }
+
+ render_date_range_fields(action_method, parent) {
+ let date_field = $(parent).find('.date-field');
+
+ if (!date_field.length) {
+ let date_field_wrapper = $(
+ `<div class="date-field pull-right"></div>`
+ ).appendTo(parent);
+
+ let date_range_field = frappe.ui.form.make_control({
+ df: {
+ fieldtype: 'DateRange',
+ fieldname: 'from_date',
+ placeholder: 'Date Range',
+ input_class: 'input-xs',
+ reqd: 1,
+ change: () => {
+ let selected_date_range = date_range_field.get_value();
+ if (selected_date_range && selected_date_range.length === 2) {
+ this[action_method](selected_date_range);
+ }
+ }
+ },
+ parent: date_field_wrapper,
+ render_input: 1
+ });
+ } else if (!date_field.is(':visible')) {
+ date_field.show();
+ }
+ }
+
+ show_therapy_progress() {
+ let me = this;
+ let therapy_type = frappe.ui.form.make_control({
+ parent: $('.therapy-type-search'),
+ df: {
+ fieldtype: 'Link',
+ options: 'Therapy Type',
+ fieldname: 'therapy_type',
+ placeholder: __('Select Therapy Type'),
+ only_select: true,
+ change: () => {
+ if (me.therapy_type != therapy_type.get_value() && therapy_type.get_value()) {
+ me.therapy_type = therapy_type.get_value();
+ me.render_therapy_progress_chart();
+ }
+ }
+ }
+ });
+ therapy_type.refresh();
+ this.create_time_span_filters('render_therapy_progress_chart', '.therapy-progress');
+ }
+
+ render_therapy_progress_chart(time_span='Last Month') {
+ if (!this.therapy_type) return;
+
+ frappe.xcall(
+ 'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_progress_data', {
+ patient: this.patient_id,
+ therapy_type: this.therapy_type,
+ time_span: time_span
+ }
+ ).then(chart => {
+ let data = {
+ labels: chart.labels,
+ datasets: chart.datasets
+ }
+ let parent = '.therapy-progress-line-chart';
+ if (!chart.labels.length) {
+ this.show_null_state(parent);
+ } else {
+ if (!this.therapy_line_chart) {
+ this.therapy_line_chart = new frappe.Chart(parent, {
+ type: 'axis-mixed',
+ height: 250,
+ data: data,
+ lineOptions: {
+ regionFill: 1
+ },
+ axisOptions: {
+ xIsSeries: 1
+ },
+ });
+ } else {
+ $(parent).find('.chart-container').show();
+ $(parent).find('.chart-empty-state').hide();
+ this.therapy_line_chart.update(data);
+ }
+ }
+ });
+ }
+
+ show_assessment_results() {
+ let me = this;
+ let assessment_template = frappe.ui.form.make_control({
+ parent: $('.assessment-template-search'),
+ df: {
+ fieldtype: 'Link',
+ options: 'Patient Assessment Template',
+ fieldname: 'assessment_template',
+ placeholder: __('Select Assessment Template'),
+ only_select: true,
+ change: () => {
+ if (me.assessment_template != assessment_template.get_value() && assessment_template.get_value()) {
+ me.assessment_template = assessment_template.get_value();
+ me.render_assessment_result_chart();
+ }
+ }
+ }
+ });
+ assessment_template.refresh();
+ this.create_time_span_filters('render_assessment_result_chart', '.assessment-results');
+ }
+
+ render_assessment_result_chart(time_span='Last Month') {
+ if (!this.assessment_template) return;
+
+ frappe.xcall(
+ 'erpnext.healthcare.page.patient_progress.patient_progress.get_patient_assessment_data', {
+ patient: this.patient_id,
+ assessment_template: this.assessment_template,
+ time_span: time_span
+ }
+ ).then(chart => {
+ let data = {
+ labels: chart.labels,
+ datasets: chart.datasets,
+ yMarkers: [
+ { label: 'Max Score', value: chart.max_score }
+ ],
+ }
+ let parent = '.assessment-results-line-chart';
+ if (!chart.labels.length) {
+ this.show_null_state(parent);
+ } else {
+ if (!this.assessment_line_chart) {
+ this.assessment_line_chart = new frappe.Chart(parent, {
+ type: 'axis-mixed',
+ height: 250,
+ data: data,
+ lineOptions: {
+ regionFill: 1
+ },
+ axisOptions: {
+ xIsSeries: 1
+ },
+ tooltipOptions: {
+ formatTooltipY: d => d + __(' out of ') + chart.max_score
+ }
+ });
+ } else {
+ $(parent).find('.chart-container').show();
+ $(parent).find('.chart-empty-state').hide();
+ this.assessment_line_chart.update(data);
+ }
+ }
+ });
+ }
+
+ show_therapy_assessment_correlation() {
+ let me = this;
+ let assessment = frappe.ui.form.make_control({
+ parent: $('.assessment-correlation-template-search'),
+ df: {
+ fieldtype: 'Link',
+ options: 'Patient Assessment Template',
+ fieldname: 'assessment',
+ placeholder: __('Select Assessment Template'),
+ only_select: true,
+ change: () => {
+ if (me.assessment != assessment.get_value() && assessment.get_value()) {
+ me.assessment = assessment.get_value();
+ me.render_therapy_assessment_correlation_chart();
+ }
+ }
+ }
+ });
+ assessment.refresh();
+ this.create_time_span_filters('render_therapy_assessment_correlation_chart', '.therapy-assessment-correlation');
+ }
+
+ render_therapy_assessment_correlation_chart(time_span='Last Month') {
+ if (!this.assessment) return;
+
+ frappe.xcall(
+ 'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_assessment_correlation_data', {
+ patient: this.patient_id,
+ assessment_template: this.assessment,
+ time_span: time_span
+ }
+ ).then(chart => {
+ let data = {
+ labels: chart.labels,
+ datasets: chart.datasets,
+ yMarkers: [
+ { label: 'Max Score', value: chart.max_score }
+ ],
+ }
+ let parent = '.therapy-assessment-correlation-chart';
+ if (!chart.labels.length) {
+ this.show_null_state(parent);
+ } else {
+ if (!this.correlation_chart) {
+ this.correlation_chart = new frappe.Chart(parent, {
+ type: 'axis-mixed',
+ height: 300,
+ data: data,
+ axisOptions: {
+ xIsSeries: 1
+ }
+ });
+ } else {
+ $(parent).find('.chart-container').show();
+ $(parent).find('.chart-empty-state').hide();
+ this.correlation_chart.update(data);
+ }
+ }
+ });
+ }
+
+ show_assessment_parameter_progress() {
+ let me = this;
+ let parameter = frappe.ui.form.make_control({
+ parent: $('.assessment-parameter-search'),
+ df: {
+ fieldtype: 'Link',
+ options: 'Patient Assessment Parameter',
+ fieldname: 'assessment',
+ placeholder: __('Select Assessment Parameter'),
+ only_select: true,
+ change: () => {
+ if (me.parameter != parameter.get_value() && parameter.get_value()) {
+ me.parameter = parameter.get_value();
+ me.render_assessment_parameter_progress_chart();
+ }
+ }
+ }
+ });
+ parameter.refresh();
+ this.create_time_span_filters('render_assessment_parameter_progress_chart', '.assessment-parameter-progress');
+ }
+
+ render_assessment_parameter_progress_chart(time_span='Last Month') {
+ if (!this.parameter) return;
+
+ frappe.xcall(
+ 'erpnext.healthcare.page.patient_progress.patient_progress.get_assessment_parameter_data', {
+ patient: this.patient_id,
+ parameter: this.parameter,
+ time_span: time_span
+ }
+ ).then(chart => {
+ let data = {
+ labels: chart.labels,
+ datasets: chart.datasets
+ }
+ let parent = '.assessment-parameter-progress-chart';
+ if (!chart.labels.length) {
+ this.show_null_state(parent);
+ } else {
+ if (!this.parameter_chart) {
+ this.parameter_chart = new frappe.Chart(parent, {
+ type: 'line',
+ height: 250,
+ data: data,
+ lineOptions: {
+ regionFill: 1
+ },
+ axisOptions: {
+ xIsSeries: 1
+ },
+ tooltipOptions: {
+ formatTooltipY: d => d + '%'
+ }
+ });
+ } else {
+ $(parent).find('.chart-container').show();
+ $(parent).find('.chart-empty-state').hide();
+ this.parameter_chart.update(data);
+ }
+ }
+ });
+ }
+
+ show_null_state(parent) {
+ let null_state = $(parent).find('.chart-empty-state');
+ if (null_state.length) {
+ $(null_state).show();
+ } else {
+ null_state = $(
+ `<div class="chart-empty-state text-muted text-center" style="margin-bottom: 20px;">${__(
+ "No Data..."
+ )}</div>`
+ );
+ $(parent).append(null_state);
+ }
+ $(parent).find('.chart-container').hide();
+ }
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.json b/erpnext/healthcare/page/patient_progress/patient_progress.json
new file mode 100644
index 0000000..0175cb9
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.json
@@ -0,0 +1,33 @@
+{
+ "content": null,
+ "creation": "2020-06-12 15:46:23.111928",
+ "docstatus": 0,
+ "doctype": "Page",
+ "idx": 0,
+ "modified": "2020-07-23 21:45:45.540055",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "patient-progress",
+ "owner": "Administrator",
+ "page_name": "patient-progress",
+ "restrict_to_domain": "Healthcare",
+ "roles": [
+ {
+ "role": "Healthcare Administrator"
+ },
+ {
+ "role": "Physician"
+ },
+ {
+ "role": "Patient"
+ },
+ {
+ "role": "System Manager"
+ }
+ ],
+ "script": null,
+ "standard": "Yes",
+ "style": null,
+ "system_page": 0,
+ "title": "Patient Progress"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.py b/erpnext/healthcare/page/patient_progress/patient_progress.py
new file mode 100644
index 0000000..a04fb2b
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.py
@@ -0,0 +1,197 @@
+import frappe
+from datetime import datetime
+from frappe import _
+from frappe.utils import getdate, get_timespan_date_range
+import json
+
+@frappe.whitelist()
+def get_therapy_sessions_count(patient):
+ total = frappe.db.count('Therapy Session', filters={
+ 'docstatus': 1,
+ 'patient': patient
+ })
+
+ month_start = datetime.today().replace(day=1)
+ this_month = frappe.db.count('Therapy Session', filters={
+ 'creation': ['>', month_start],
+ 'docstatus': 1,
+ 'patient': patient
+ })
+
+ return {
+ 'total_therapy_sessions': total,
+ 'therapy_sessions_this_month': this_month
+ }
+
+
+@frappe.whitelist()
+def get_patient_heatmap_data(patient, date):
+ return dict(frappe.db.sql("""
+ SELECT
+ unix_timestamp(communication_date), count(*)
+ FROM
+ `tabPatient Medical Record`
+ WHERE
+ communication_date > subdate(%(date)s, interval 1 year) and
+ communication_date < subdate(%(date)s, interval -1 year) and
+ patient = %(patient)s
+ GROUP BY communication_date
+ ORDER BY communication_date asc""", {'date': date, 'patient': patient}))
+
+
+@frappe.whitelist()
+def get_therapy_sessions_distribution_data(patient, field):
+ if field == 'therapy_type':
+ result = frappe.db.get_all('Therapy Session',
+ filters = {'patient': patient, 'docstatus': 1},
+ group_by = field,
+ order_by = field,
+ fields = [field, 'count(*)'],
+ as_list = True)
+
+ elif field == 'exercise_type':
+ data = frappe.db.get_all('Therapy Session', filters={
+ 'docstatus': 1,
+ 'patient': patient
+ }, as_list=True)
+ therapy_sessions = [entry[0] for entry in data]
+
+ result = frappe.db.get_all('Exercise',
+ filters = {
+ 'parenttype': 'Therapy Session',
+ 'parent': ['in', therapy_sessions],
+ 'docstatus': 1
+ },
+ group_by = field,
+ order_by = field,
+ fields = [field, 'count(*)'],
+ as_list = True)
+
+ return {
+ 'labels': [r[0] for r in result if r[0] != None],
+ 'datasets': [{
+ 'values': [r[1] for r in result]
+ }]
+ }
+
+
+@frappe.whitelist()
+def get_therapy_progress_data(patient, therapy_type, time_span):
+ date_range = get_date_range(time_span)
+ query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'therapy_type': therapy_type, 'patient': patient}
+ result = frappe.db.sql("""
+ SELECT
+ start_date, total_counts_targeted, total_counts_completed
+ FROM
+ `tabTherapy Session`
+ WHERE
+ start_date BETWEEN %(from_date)s AND %(to_date)s and
+ docstatus = 1 and
+ therapy_type = %(therapy_type)s and
+ patient = %(patient)s
+ ORDER BY start_date""", query_values, as_list=1)
+
+ return {
+ 'labels': [r[0] for r in result if r[0] != None],
+ 'datasets': [
+ { 'name': _('Targetted'), 'values': [r[1] for r in result if r[0] != None] },
+ { 'name': _('Completed'), 'values': [r[2] for r in result if r[0] != None] }
+ ]
+ }
+
+@frappe.whitelist()
+def get_patient_assessment_data(patient, assessment_template, time_span):
+ date_range = get_date_range(time_span)
+ query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'assessment_template': assessment_template, 'patient': patient}
+ result = frappe.db.sql("""
+ SELECT
+ assessment_datetime, total_score, total_score_obtained
+ FROM
+ `tabPatient Assessment`
+ WHERE
+ DATE(assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and
+ docstatus = 1 and
+ assessment_template = %(assessment_template)s and
+ patient = %(patient)s
+ ORDER BY assessment_datetime""", query_values, as_list=1)
+
+ return {
+ 'labels': [getdate(r[0]) for r in result if r[0] != None],
+ 'datasets': [
+ { 'name': _('Score Obtained'), 'values': [r[2] for r in result if r[0] != None] }
+ ],
+ 'max_score': result[0][1] if result else None
+ }
+
+@frappe.whitelist()
+def get_therapy_assessment_correlation_data(patient, assessment_template, time_span):
+ date_range = get_date_range(time_span)
+ query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'assessment': assessment_template, 'patient': patient}
+ result = frappe.db.sql("""
+ SELECT
+ therapy.therapy_type, count(*), avg(assessment.total_score_obtained), total_score
+ FROM
+ `tabPatient Assessment` assessment INNER JOIN `tabTherapy Session` therapy
+ ON
+ assessment.therapy_session = therapy.name
+ WHERE
+ DATE(assessment.assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and
+ assessment.docstatus = 1 and
+ assessment.patient = %(patient)s and
+ assessment.assessment_template = %(assessment)s
+ GROUP BY therapy.therapy_type
+ """, query_values, as_list=1)
+
+ return {
+ 'labels': [r[0] for r in result if r[0] != None],
+ 'datasets': [
+ { 'name': _('Sessions'), 'chartType': 'bar', 'values': [r[1] for r in result if r[0] != None] },
+ { 'name': _('Average Score'), 'chartType': 'line', 'values': [round(r[2], 2) for r in result if r[0] != None] }
+ ],
+ 'max_score': result[0][1] if result else None
+ }
+
+@frappe.whitelist()
+def get_assessment_parameter_data(patient, parameter, time_span):
+ date_range = get_date_range(time_span)
+ query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'parameter': parameter, 'patient': patient}
+ results = frappe.db.sql("""
+ SELECT
+ assessment.assessment_datetime,
+ sheet.score,
+ template.scale_max
+ FROM
+ `tabPatient Assessment Sheet` sheet
+ INNER JOIN `tabPatient Assessment` assessment
+ ON sheet.parent = assessment.name
+ INNER JOIN `tabPatient Assessment Template` template
+ ON template.name = assessment.assessment_template
+ WHERE
+ DATE(assessment.assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and
+ assessment.docstatus = 1 and
+ sheet.parameter = %(parameter)s and
+ assessment.patient = %(patient)s
+ ORDER BY
+ assessment.assessment_datetime asc
+ """, query_values, as_list=1)
+
+ score_percentages = []
+ for r in results:
+ if r[2] != 0 and r[0] != None:
+ score = round((int(r[1]) / int(r[2])) * 100, 2)
+ score_percentages.append(score)
+
+ return {
+ 'labels': [getdate(r[0]) for r in results if r[0] != None],
+ 'datasets': [
+ { 'name': _('Score'), 'values': score_percentages }
+ ]
+ }
+
+def get_date_range(time_span):
+ try:
+ time_span = json.loads(time_span)
+ return time_span
+ except json.decoder.JSONDecodeError:
+ return get_timespan_date_range(time_span.lower())
+
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html b/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html
new file mode 100644
index 0000000..cd62dd3
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html
@@ -0,0 +1,29 @@
+<div class="patient-progress-sidebar">
+ <div class="patient-image-container">
+ {% if patient_image %}
+ <div class="patient-image" src={{patient_image}} style="background-image: url(\'{%= patient_image %}\')"></div>
+ {% endif %}
+ </div>
+ <div class="patient-details">
+ {% if patient_name %}
+ <p class="patient-name bold">{{patient_name}}</p>
+ {% endif %}
+ {% if patient_gender %}
+ <p class="patient-gender text-muted">{%=__("Gender: ") %} {{patient_gender}}</p>
+ {% endif %}
+ {% if patient_mobile %}
+ <p class="patient-mobile text-muted">{%=__("Contact: ") %} {{patient_mobile}}</p>
+ {% endif %}
+ {% if total_therapy_sessions %}
+ <p class="patient-sessions text-muted">{%=__("Total Therapy Sessions: ") %} {{total_therapy_sessions}}</p>
+ {% endif %}
+ {% if therapy_sessions_this_month %}
+ <p class="patient-sessions text-muted">{%=__("Monthly Therapy Sessions: ") %} {{therapy_sessions_this_month}}</p>
+ {% endif %}
+ </div>
+ <div class="important-links">
+ <p><a class="patient-profile-link">{%=__("Patient Profile") %}</a></p>
+ <p><a class="therapy-plan-link">{%=__("Therapy Plan") %}</a></p>
+ <p><a class="patient-history">{%=__("Patient History") %}</a></p>
+ </div>
+</div>
\ No newline at end of file
diff --git a/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json b/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json
index e8e95d8..f7d1676 100644
--- a/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json
+++ b/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json
@@ -7,16 +7,17 @@
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
- "html": "<div >\n {% if letter_head and not no_letterhead -%}\n <div class=\"letter-head\">{{ letter_head }}</div>\n <hr>\n {%- endif %}\n\n {% if (doc.docstatus != 1) %}\n <b>Lab Tests have to be Submitted for Print .. !</b>\n {% elif (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"lab_test_approval_required\") == '1' and doc.approval_status != \"Approved\") %}\n <b>Lab Tests have to be Approved for Print .. !</b>\n {%- else -%}\n <div class=\"row section-break\">\n <div class=\"col-xs-6 column-break\">\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Patient</label>\n </div>\n {% if doc.patient %}\n <div class=\"col-xs-7 value\">\n <strong>: </strong>{{doc.patient}}\n </div>\n {% else %}\n <div class=\"col-xs-7 value\">\n <strong>: </strong><em>Patient Name</em>\n </div>\n {%- endif -%}\n </div>\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Age</label>\n </div>\n <div class=\"col-xs-7 value\">\n <strong>: </strong> {{doc.patient_age}}\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Gender</label>\n </div>\n <div class=\"col-xs-7 value\">\n <strong>: </strong> {{doc.patient_sex}}\n </div>\n </div>\n\n </div>\n\n <div class=\"col-xs-6 column-break\">\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Practitioner</label>\n </div>\n {% if doc.practitioner %}\n <div class=\"col-xs-7 text-left value\">\n <strong>: </strong>{{doc.practitioner}}\n </div>\n {%- endif -%}\n </div>\n\n {% if doc.sample_date %}\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Sample Date</label>\n </div>\n <div class=\"col-xs-7 text-left value\">\n <strong>: </strong>{{doc.sample_date}}\n </div>\n </div>\n {%- endif -%}\n\n {% if doc.result_date %}\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Result Date</label>\n </div>\n <div class=\"col-xs-7 text-left value\">\n <strong>: </strong>{{doc.result_date}}\n </div>\n </div>\n {%- endif -%}\n\n </div>\n\n </div>\n\n <div align=\"center\">\n <hr><h4 class=\"text-uppercase\"><b><u>Department of {{doc.department}}</u></b></h4>\n </div>\n\n <table class=\"table\">\n <tbody>\n {%- if doc.normal_test_items -%}\n <tr>\n <th>Name of Test</th>\n <th class=\"text-left\">Result</th>\n <th class=\"text-right\">Normal Range</th>\n </tr>\n\n {%- if doc.normal_test_items|length > 1 %}\n <tr><td style=\"width: 40%;\"> <b>{{ doc.lab_test_name }}</b> </td><td></td></tr>\n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n <tr>\n <td style=\"width: 40%;border:none;\">\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}<b>{{ row.lab_test_name }}</b>\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n </td>\n\n <td style=\"width: 20%;text-align: left;border:none;\">\n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%} \n {%- if row.lab_test_uom -%}{{ row.lab_test_uom }}{%- endif -%}\n </td>\n\n <td style=\"width: 30%;text-align: right;border:none;\">\n <div style=\"border: 0px;\">\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n </div>\n </td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n </tbody>\n </table>\n\n <table class=\"table\">\n <tbody>\n {%- if doc.special_test_items -%}\n <tr>\n <th>Name of Test</th>\n <th class=\"text-left\">Result</th>\n </tr>\n <tr><td style=\"width: 30%;border:none;\"> <b>{{ doc.lab_test_name }}</b> </td><td></td></tr>\n {%- for row in doc.special_test_items -%}\n <tr>\n <td style=\"width: 30%;border:none;\">   {{ row.lab_test_particulars }} </td>\n <td style=\"width: 70%;text-align: left;border:none;\">\n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%}\n </td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n\n {%- if doc.sensitivity_test_items -%}\n <tr>\n <th>Antibiotic</th>\n <th class=\"text-left\">Sensitivity</th>\n </tr>\n {%- for row in doc.sensitivity_test_items -%}\n <tr>\n <td style=\"width: 30%;border:none;\"> {{ row.antibiotic }} </td>\n <td style=\"width: 70%;text-align: left;border:none;\">{{ row.antibiotic_sensitivity }}</td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n\n </tbody>\n </table>\n {%- endif -%}\n\n <div align=\"right\">\n {%- if (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"employee_name_and_designation_in_print\") == '1') -%}\n <h6 class=\"text-uppercase\"><b>{{doc.employee_name}}</b></h6>\n <h6 class=\"text-uppercase\"><b>{{doc.employee_designation}}</b></h6>\n {%- else -%}\n <h6 ><b>{{frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") }}</b></h6>\n {%- endif -%}\n </div>\n</div>\n",
+ "html": "<div >\n {% if letter_head and not no_letterhead -%}\n <div class=\"letter-head\">{{ letter_head }}</div>\n <hr>\n {%- endif %}\n\n {% if (doc.docstatus != 1) %}\n <div><h2 class=\"text-uppercase text-center\"><b>WORKSHEET</b></h2></div>\n\t<br/>\n\t<div class=\"row section-break\">\n <div class=\"col-xs-6 column-break\">\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Patient</label>\n </div>\n {% if doc.patient_name %}\n <div class=\"col-xs-7 value\">\n {{ doc.patient_name }}\n </div>\n {% else %}\n <div class=\"col-xs-7 value\">\n {{ doc.patient }}\n </div>\n {%- endif -%}\n </div>\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Age</label>\n </div>\n <div class=\"col-xs-7 value\">\n {{ doc.patient_age or '' }}\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Gender</label>\n </div>\n <div class=\"col-xs-7 value\">\n {{ doc.patient_sex or '' }}\n </div>\n </div>\n\n </div>\n\n <div class=\"col-xs-6 column-break\">\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Practitioner</label>\n </div>\n {% if doc.practitioner_name %}\n <div class=\"col-xs-7 text-left value\">\n {{ doc.practitioner_name }}\n </div>\n {% else %}\n\t\t\t{% if doc.referring_practitioner_name %}\n <div class=\"col-xs-7 text-left value\">\n {{ doc.referring_practitioner_name }}\n </div>\n\t\t {% endif %}\n {%- endif -%}\n </div>\n\n {% if doc.sample_date %}\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Sample Date</label>\n </div>\n <div class=\"col-xs-7 text-left value\">\n {{ doc.sample_date }}\n </div>\n </div>\n {%- endif -%}\n </div>\n </div>\n\n\t<div>\n <hr><h4 class=\"text-uppercase text-center\"><b><u>Department of {{ doc.department }}</u></b></h4>\n </div>\n\n\t<table class=\"table\">\n <tbody>\n {%- if doc.normal_test_items -%}\n <tr>\n <th>Name of Test</th>\n <th class=\"text-left\">Result</th>\n <th class=\"text-right\">Normal Range</th>\n </tr>\n\n {%- if doc.normal_test_items|length > 1 %}\n <tr><td style=\"width: 40%;\"> <b>{{ doc.lab_test_name }}</b> </td><td></td></tr>\n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n <tr>\n <td style=\"width: 40%;border:none;\">\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}<b>{{ row.lab_test_name }}</b>\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n </td>\n\n <td style=\"width: 20%;text-align: right;border:none;\">\n {%- if row.lab_test_uom -%} {{ row.lab_test_uom }}{%- endif -%}\n </td>\n\n <td style=\"width: 30%;text-align: right;border:none;\">\n <div style=\"border: 0px;\">\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n </div>\n </td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n </tbody>\n </table>\n\n\t<table class=\"table\">\n <tbody>\n {%- if doc.descriptive_test_items -%}\n <tr>\n <th>Name of Test</th>\n <th class=\"text-left\">Result</th>\n </tr>\n <tr><td style=\"width: 30%;border:none;\"> <b>{{ doc.lab_test_name }}</b> </td><td></td></tr>\n\t\t\t{% set gr_lab_test_name = {'ltname': ''} %}\n {%- for row in doc.descriptive_test_items -%}\n\t\t\t{%- if row.lab_test_name -%}\n\t\t\t{%- if row.lab_test_name != gr_lab_test_name.ltname -%}\n\t\t\t<tr>\n\t\t\t\t<td style=\"width: 30%;border:none;\">  {{ row.lab_test_name }} </td>\n\t\t\t\t<td style=\"width: 70%;text-align: left;border:none;\"></td>\n\t\t\t</tr>\n\t\t\t{% if gr_lab_test_name.update({'ltname': row.lab_test_name}) %} {% endif %}\n\t\t\t{%- endif -%}\n\t\t\t{%- endif -%}\n <tr>\n <td style=\"width: 30%;border:none;\">   {{ row.lab_test_particulars }} </td>\n <td style=\"width: 70%;text-align: left;border:none;\"></td>\n </tr>\n {%- endfor -%}\n {%- endif -%}\n </tbody>\n </table>\n <div>\n {% if doc.worksheet_instructions %}\n <hr>\n <b>Instructions</b>\n {{ doc.worksheet_instructions }}\n {%- endif -%}\n</div>\n {% elif (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"require_test_result_approval\") == '1' and doc.status != \"Approved\") %}\n <b>Lab Tests have to be Approved for Print .. !</b>\n {%- else -%}\n <div class=\"row section-break\">\n <div class=\"col-xs-6 column-break\">\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Patient</label>\n </div>\n {% if doc.patient_name %}\n <div class=\"col-xs-7 value\">\n {{ doc.patient_name }}\n </div>\n {% else %}\n <div class=\"col-xs-7 value\">\n {{ doc.patient }}\n </div>\n {%- endif -%}\n </div>\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Age</label>\n </div>\n <div class=\"col-xs-7 value\">\n {{ doc.patient_age or '' }}\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Gender</label>\n </div>\n <div class=\"col-xs-7 value\">\n {{ doc.patient_sex or '' }}\n </div>\n </div>\n\n </div>\n\n <div class=\"col-xs-6 column-break\">\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Practitioner</label>\n </div>\n {% if doc.practitioner_name %}\n <div class=\"col-xs-7 text-left value\">\n {{ doc.practitioner_name }}\n </div>\n\t\t{% else %}\n\t\t {% if doc.referring_practitioner_name %}\n <div class=\"col-xs-7 text-left value\">\n {{ doc.referring_practitioner_name }}\n </div>\n\t\t\t{% endif %}\n {%- endif -%}\n </div>\n\n {% if doc.sample_date %}\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Sample Date</label>\n </div>\n <div class=\"col-xs-7 text-left value\">\n {{ doc.sample_date }}\n </div>\n </div>\n {%- endif -%}\n\n {% if doc.result_date %}\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Result Date</label>\n </div>\n <div class=\"col-xs-7 text-left value\">\n {{ doc.result_date }}\n </div>\n </div>\n {%- endif -%}\n\n </div>\n\n </div>\n\n <div>\n <hr><h4 class=\"text-uppercase text-center\"><b><u>Department of {{ doc.department }}</u></b></h4>\n </div>\n\n\t<div>\n\t\t{% if doc.result_legend and (doc.legend_print_position == \"Top\" or doc.legend_print_position == \"Both\")%}\n\t\t<b>Result Legend:</b>\n\t\t{{ doc.result_legend }}\n\t\t{%- endif -%}\n\t</div>\n\n <table class=\"table\">\n <tbody>\n {%- if doc.normal_test_items -%}\n <tr>\n <th>Name of Test</th>\n <th class=\"text-left\">Result</th>\n <th class=\"text-right\">Normal Range</th>\n </tr>\n\n {%- if doc.normal_test_items|length > 1 %}\n <tr><td style=\"width: 40%;\"> <b>{{ doc.lab_test_name }}</b> </td><td></td></tr>\n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n <tr>\n <td style=\"width: 40%;border:none;\">\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}<b>{{ row.lab_test_name }}</b>\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n </td>\n\n <td style=\"width: 20%;text-align: left;border:none;\">\n\t\t\t\t\t{%- if row.result_value -%}\n\t\t\t\t\t\t{%- if row.bold -%}<b>{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}<u>{% endif %}\n\t\t\t\t\t\t{%- if row.italic -%}<i>{% endif %}\n {{ row.result_value }}\n {%- if row.lab_test_uom -%} {{ row.lab_test_uom }}{%- endif -%}\n\t\t\t\t\t\t{%- if row.italic -%}</i>{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}</u>{% endif %}\n\t\t\t\t\t\t{%- if row.bold -%}</b>{% endif %}\n\t\t\t\t\t{%- endif -%}\n \n\t\t\t\t\t{%- if row.secondary_uom and row.conversion_factor and row.secondary_uom_result -%}\n\t\t\t\t\t\t<br/>\n\t\t\t\t\t\t{%- if row.bold -%}<b>{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}<u>{% endif %}\n\t\t\t\t\t\t{%- if row.italic -%}<i>{% endif %}\n {{ row.secondary_uom_result }}\n  {{ row.secondary_uom }}\n\t\t\t\t\t\t{%- if row.italic -%}</i>{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}</u>{% endif %}\n\t\t\t\t\t\t{%- if row.bold -%}</b>{% endif %}\n\t\t\t\t\t\t \n\t\t\t\t\t{%- endif -%}\n </td>\n\n <td style=\"width: 30%;text-align: right;border:none;\">\n <div style=\"border: 0px;\">\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n </div>\n </td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n </tbody>\n </table>\n\n <table class=\"table\">\n <tbody>\n {%- if doc.descriptive_test_items -%}\n <tr>\n <th>Name of Test</th>\n <th class=\"text-left\">Result</th>\n </tr>\n <tr><td style=\"width: 30%;border:none;\"> <b>{{ doc.lab_test_name }}</b> </td><td></td></tr>\n\t\t\t{% set gr_lab_test_name = {'ltname': ''} %}\n {%- for row in doc.descriptive_test_items -%}\n\t\t\t{%- if row.lab_test_name -%}\n\t\t\t{%- if row.lab_test_name != gr_lab_test_name.ltname -%}\n\t\t\t<tr>\n\t\t\t\t<td style=\"width: 30%;border:none;\">  {{ row.lab_test_name }} </td>\n\t\t\t\t<td style=\"width: 70%;text-align: left;border:none;\"></td>\n\t\t\t</tr>\n\t\t\t{% if gr_lab_test_name.update({'ltname': row.lab_test_name}) %} {% endif %}\n\t\t\t{%- endif -%}\n\t\t\t{%- endif -%}\n <tr>\n <td style=\"width: 30%;border:none;\">   {{ row.lab_test_particulars }} </td>\n <td style=\"width: 70%;text-align: left;border:none;\">\n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%}\n </td>\n </tr>\n {%- endfor -%}\n {%- endif -%}\n\n\t\t\t{%- if doc.organisms -%}\n\t\t\t<tr>\n\t\t\t\t<th>Organism</th>\n\t\t\t\t<th class=\"text-left\">Colony Population</th>\n\t\t\t</tr>\n\t\t\t{%- for row in doc.organisms -%}\n\t\t\t<tr>\n\t\t\t\t<td style=\"width: 30%;border:none;\"> {{ row.organism }} </td>\n\t\t\t\t<td style=\"width: 60%;text-align: left;border:none;\">\n\t\t\t\t\t{{ row.colony_population }}\n\t\t\t\t\t{% if row.colony_uom %}\n\t\t\t\t\t\t{{ row.colony_uom }}\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t\t{%- endfor -%}\n\t\t\t{%- endif -%}\n\n\t\t\t{%- if doc.sensitivity_test_items -%}\n\t\t\t<tr>\n\t\t\t\t<th>Antibiotic</th>\n\t\t\t\t<th class=\"text-left\">Sensitivity</th>\n\t\t\t</tr>\n\t\t\t{%- for row in doc.sensitivity_test_items -%}\n\t\t\t<tr>\n\t\t\t\t<td style=\"width: 30%;border:none;\"> {{ row.antibiotic }} </td>\n\t\t\t\t<td style=\"width: 70%;text-align: left;border:none;\">{{ row.antibiotic_sensitivity }}</td>\n\t\t\t</tr>\n\t\t\t{%- endfor -%}\n\t\t\t{%- endif -%}\n\n </tbody>\n </table>\n <div>\n {% if doc.custom_result %}\n <br/>\n <div> {{ doc.custom_result }} </div>\n {%- endif -%}\n </div>\n\n <div>\n {% if doc.lab_test_comment %}\n <br/>\n <b>Comments</b>\n {{ doc.lab_test_comment }}\n {%- endif -%}\n </div>\n\n <div class=\"text-right\">\n {%- if (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"employee_name_and_designation_in_print\") == '1') -%}\n {%- if doc.employee_name -%}\n <h6 class=\"text-uppercase\"><b>{{ doc.employee_name }}</b></h6>\n {%- endif -%}\n {%- if doc.employee_designation -%}\n <h6 class=\"text-uppercase\"><b>{{ doc.employee_designation }}</b></h6>\n {%- endif -%}\n {%- else -%}\n {%- if frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") -%}\n <h6 ><b>{{ frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") }}</b></h6>\n {%- endif -%}\n {%- endif -%}\n </div>\n\n <div>\n {% if doc.result_legend and (doc.legend_print_position == \"Bottom\" or doc.legend_print_position == \"Both\" or doc.legend_print_position == \"\")%}\n <hr>\n <b>Result Legend</b>\n {{ doc.result_legend }}\n {%- endif -%}\n </div>\n {%- endif -%}\n</div>",
"idx": 0,
"line_breaks": 0,
- "modified": "2018-09-04 12:03:47.066918",
+ "modified": "2020-07-08 15:34:28.866798",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Test Print",
"owner": "Administrator",
"print_format_builder": 0,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
"show_section_headings": 0,
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/healthcare/web_form/lab_test/lab_test.json b/erpnext/healthcare/web_form/lab_test/lab_test.json
index 88a9756..3509917 100644
--- a/erpnext/healthcare/web_form/lab_test/lab_test.json
+++ b/erpnext/healthcare/web_form/lab_test/lab_test.json
@@ -1,255 +1,459 @@
{
- "accept_payment": 0,
- "allow_comments": 0,
- "allow_delete": 0,
- "allow_edit": 1,
- "allow_incomplete": 0,
- "allow_multiple": 1,
- "allow_print": 1,
- "amount": 0.0,
- "amount_based_on_field": 0,
- "creation": "2017-06-06 16:12:33.052258",
- "currency": "INR",
- "doc_type": "Lab Test",
- "docstatus": 0,
- "doctype": "Web Form",
- "idx": 0,
- "introduction_text": "Lab Test",
- "is_standard": 1,
- "login_required": 1,
- "max_attachment_size": 0,
- "modified": "2018-09-04 08:50:41.314546",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "lab-test",
- "owner": "Administrator",
- "payment_button_label": "Buy Now",
- "print_format": "Lab Test Print",
- "published": 1,
- "route": "lab-test",
- "show_in_grid": 0,
- "show_sidebar": 1,
- "sidebar_items": [],
- "success_url": "/lab-test",
- "title": "Lab Test",
+ "accept_payment": 0,
+ "allow_comments": 1,
+ "allow_delete": 0,
+ "allow_edit": 1,
+ "allow_incomplete": 0,
+ "allow_multiple": 1,
+ "allow_print": 1,
+ "amount": 0.0,
+ "amount_based_on_field": 0,
+ "creation": "2017-06-06 16:12:33.052258",
+ "currency": "INR",
+ "doc_type": "Lab Test",
+ "docstatus": 0,
+ "doctype": "Web Form",
+ "idx": 0,
+ "introduction_text": "Lab Test",
+ "is_standard": 1,
+ "login_required": 1,
+ "max_attachment_size": 0,
+ "modified": "2020-06-22 12:59:49.126398",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "lab-test",
+ "owner": "Administrator",
+ "payment_button_label": "Buy Now",
+ "print_format": "Lab Test Print",
+ "published": 1,
+ "route": "lab-test",
+ "route_to_success_link": 0,
+ "show_attachments": 0,
+ "show_in_grid": 0,
+ "show_sidebar": 1,
+ "sidebar_items": [],
+ "success_url": "/lab-test",
+ "title": "Lab Test",
"web_form_fields": [
{
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "hidden": 0,
- "label": "Series",
- "max_length": 0,
- "max_value": 0,
- "options": "LP-",
- "read_only": 0,
- "reqd": 1,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "lab_test_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Test Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 1,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "default": "0",
- "fieldname": "invoiced",
- "fieldtype": "Check",
- "hidden": 0,
- "label": "Invoiced",
- "max_length": 0,
- "max_value": 0,
- "options": "",
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Department",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Medical Department",
+ "read_only": 1,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "patient",
- "fieldtype": "Link",
- "hidden": 0,
- "label": "Patient",
- "max_length": 0,
- "max_value": 0,
- "options": "Patient",
- "read_only": 0,
- "reqd": 1,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "column_break_26",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "patient_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Patient Name",
- "max_length": 0,
- "max_value": 0,
- "options": "patient.patient_name",
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Company",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Company",
+ "read_only": 0,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "practitioner",
- "fieldtype": "Link",
- "hidden": 0,
- "label": "Healthcare Practitioner",
- "max_length": 0,
- "max_value": 0,
- "options": "Healthcare Practitioner",
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "label": "Status",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Draft\nCompleted\nApproved\nRejected\nCancelled",
+ "read_only": 1,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "status",
- "fieldtype": "Select",
- "hidden": 0,
- "label": "Status",
- "max_length": 0,
- "max_value": 0,
- "options": "Draft\nCompleted\nApproved\nRejected\nCancelled",
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "submitted_date",
+ "fieldtype": "Datetime",
+ "hidden": 0,
+ "label": "Submitted Date",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "department",
- "fieldtype": "Link",
- "hidden": 0,
- "label": "Department",
- "max_length": 0,
- "max_value": 0,
- "options": "Medical Department",
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "sb_first",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "sample",
- "fieldtype": "Link",
- "hidden": 0,
- "label": "Sample ID",
- "max_length": 0,
- "max_value": 0,
- "options": "Sample Collection",
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "patient",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Patient",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Patient",
+ "read_only": 0,
+ "reqd": 1,
"show_in_filter": 0
- },
+ },
{
- "default": "",
- "fieldname": "result_date",
- "fieldtype": "Date",
- "hidden": 0,
- "label": "Result Date",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "patient_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Patient Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 1,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "report_preference",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Report Preference",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "patient_age",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Age",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 1,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "lab_test_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Test Name",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "patient_sex",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Gender",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Gender",
+ "read_only": 0,
+ "reqd": 1,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "normal_test_items",
- "fieldtype": "Table",
- "hidden": 0,
- "max_length": 0,
- "max_value": 0,
- "options": "Normal Test Items",
- "read_only": 1,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "inpatient_record",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Inpatient Record",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Inpatient Record",
+ "read_only": 1,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "special_test_items",
- "fieldtype": "Table",
- "hidden": 0,
- "max_length": 0,
- "max_value": 0,
- "options": "Special Test Items",
- "read_only": 1,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "report_preference",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Report Preference",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 1,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "sensitivity_test_items",
- "fieldtype": "Table",
- "hidden": 0,
- "max_length": 0,
- "max_value": 0,
- "options": "Sensitivity Test Items",
- "read_only": 1,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "email",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Email",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 1,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "lab_test_comment",
- "fieldtype": "Text",
- "hidden": 0,
- "label": "Comments",
- "max_length": 0,
- "max_value": 0,
- "read_only": 1,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "mobile",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Mobile",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 1,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "fieldname": "custom_result",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "label": "Custom Result",
- "max_length": 0,
- "max_value": 0,
- "read_only": 1,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "c_b",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "default": "0",
- "fieldname": "sensitivity_toggle",
- "fieldtype": "Check",
- "hidden": 1,
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "practitioner",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Requesting Practitioner",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Healthcare Practitioner",
+ "read_only": 0,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "default": "0",
- "fieldname": "special_toggle",
- "fieldtype": "Check",
- "hidden": 1,
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "practitioner_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Requesting Practitioner",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 1,
+ "reqd": 0,
"show_in_filter": 0
- },
+ },
{
- "default": "0",
- "fieldname": "normal_toggle",
- "fieldtype": "Check",
- "hidden": 1,
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0,
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "requesting_department",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Requesting Department",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Medical Department",
+ "read_only": 1,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Employee (Lab Technician)",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Employee",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Lab Technician Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 1,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "employee_designation",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Lab Technician Designation",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 1,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "sb_normal",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "lab_test_html",
+ "fieldtype": "HTML",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "normal_test_items",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Normal Test Result",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "sb_descriptive",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "descriptive_test_items",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Descriptive Test Result",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "depends_on": "special_toggle",
+ "fieldname": "organisms_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "organisms",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Organism Test Result",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "sb_sensitivity",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "sensitivity_test_items",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Sensitivity Test Result",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "sb_comments",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "lab_test_comment",
+ "fieldtype": "Text",
+ "hidden": 0,
+ "label": "Comments",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "sb_customresult",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "label": "Custom Result",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "custom_result",
+ "fieldtype": "Text Editor",
+ "hidden": 0,
+ "label": "Custom Result",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
"show_in_filter": 0
}
]
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 2a69589..95a836f 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -13,7 +13,7 @@
app_logo_url = '/assets/erpnext/images/erp-icon.svg'
-develop_version = '12.x.x-develop'
+develop_version = '13.x.x-develop'
app_include_js = "assets/js/erpnext.min.js"
app_include_css = "assets/css/erpnext.css"
@@ -234,15 +234,22 @@
"validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products"
},
"Sales Invoice": {
- "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"],
- "on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",
+ "on_submit": [
+ "erpnext.regional.create_transaction_log",
+ "erpnext.regional.italy.utils.sales_invoice_on_submit",
+ "erpnext.erpnext_integrations.taxjar_integration.create_transaction"
+ ],
+ "on_cancel": [
+ "erpnext.regional.italy.utils.sales_invoice_on_cancel",
+ "erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
+ ],
"on_trash": "erpnext.regional.check_deletion_permission"
},
"Purchase Invoice": {
- "on_submit": "erpnext.regional.india.utils.make_reverse_charge_entries"
+ "validate": "erpnext.regional.india.utils.update_grand_total_for_rcm"
},
"Payment Entry": {
- "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"],
+ "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
"on_trash": "erpnext.regional.check_deletion_permission"
},
'Address': {
@@ -261,6 +268,9 @@
},
"Email Unsubscribe": {
"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
+ },
+ ('Quotation', 'Sales Order', 'Sales Invoice'): {
+ 'validate': ["erpnext.erpnext_integrations.taxjar_integration.set_sales_tax"]
}
}
@@ -364,7 +374,8 @@
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data',
'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
- 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period'
+ 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
+ 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries'
},
'United Arab Emirates': {
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data'
@@ -541,4 +552,4 @@
{'doctype': 'Hotel Room Package', 'index': 3},
{'doctype': 'Hotel Room Type', 'index': 4}
]
-}
+}
\ No newline at end of file
diff --git a/erpnext/hr/dashboard_chart/attendance_count/attendance_count.json b/erpnext/hr/dashboard_chart/attendance_count/attendance_count.json
new file mode 100644
index 0000000..4666aec
--- /dev/null
+++ b/erpnext/hr/dashboard_chart/attendance_count/attendance_count.json
@@ -0,0 +1,27 @@
+{
+ "chart_name": "Attendance Count",
+ "chart_type": "Report",
+ "creation": "2020-07-22 11:56:32.730068",
+ "custom_options": "{\n\t\t\"type\": \"line\",\n\t\t\"axisOptions\": {\n\t\t\t\"shortenYAxisNumbers\": 1\n\t\t},\n\t\t\"tooltipOptions\": {}\n\t}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"month\":\"frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth() + 1\",\"year\":\"frappe.datetime.str_to_obj(frappe.datetime.get_today()).getFullYear();\",\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}",
+ "filters_json": "{}",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 14:32:40.334424",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Attendance Count",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Monthly Attendance Sheet",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Line",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/hr/dashboard_chart/department_wise_employee_count/department_wise_employee_count.json b/erpnext/hr/dashboard_chart/department_wise_employee_count/department_wise_employee_count.json
new file mode 100644
index 0000000..c21bfb9
--- /dev/null
+++ b/erpnext/hr/dashboard_chart/department_wise_employee_count/department_wise_employee_count.json
@@ -0,0 +1,29 @@
+{
+ "chart_name": "Department Wise Employee Count",
+ "chart_type": "Group By",
+ "creation": "2020-07-22 11:56:32.760730",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Employee",
+ "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]",
+ "group_by_based_on": "department",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 14:27:40.574194",
+ "modified": "2020-07-22 14:33:38.036794",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Department Wise Employee Count",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Donut",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/hr/dashboard_chart/department_wise_openings/department_wise_openings.json b/erpnext/hr/dashboard_chart/department_wise_openings/department_wise_openings.json
new file mode 100644
index 0000000..b1953d4
--- /dev/null
+++ b/erpnext/hr/dashboard_chart/department_wise_openings/department_wise_openings.json
@@ -0,0 +1,29 @@
+{
+ "aggregate_function_based_on": "planned_vacancies",
+ "chart_name": "Department Wise Openings",
+ "chart_type": "Group By",
+ "creation": "2020-07-22 11:56:32.849775",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Job Opening",
+ "filters_json": "[]",
+ "group_by_based_on": "department",
+ "group_by_type": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 14:33:44.834801",
+ "modified": "2020-07-22 14:34:45.273591",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Department Wise Openings",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/hr/dashboard_chart/designation_wise_employee_count/designation_wise_employee_count.json b/erpnext/hr/dashboard_chart/designation_wise_employee_count/designation_wise_employee_count.json
new file mode 100644
index 0000000..b10235c
--- /dev/null
+++ b/erpnext/hr/dashboard_chart/designation_wise_employee_count/designation_wise_employee_count.json
@@ -0,0 +1,29 @@
+{
+ "chart_name": "Designation Wise Employee Count",
+ "chart_type": "Group By",
+ "creation": "2020-07-22 11:56:32.790337",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Employee",
+ "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]",
+ "group_by_based_on": "designation",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 14:27:40.602783",
+ "modified": "2020-07-22 14:31:49.665555",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Designation Wise Employee Count",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Donut",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/hr/dashboard_chart/designation_wise_openings/designation_wise_openings.json b/erpnext/hr/dashboard_chart/designation_wise_openings/designation_wise_openings.json
new file mode 100644
index 0000000..49ea98a
--- /dev/null
+++ b/erpnext/hr/dashboard_chart/designation_wise_openings/designation_wise_openings.json
@@ -0,0 +1,30 @@
+{
+ "aggregate_function_based_on": "planned_vacancies",
+ "chart_name": "Designation Wise Openings",
+ "chart_type": "Group By",
+ "creation": "2020-07-22 11:56:32.820217",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Job Opening",
+ "dynamic_filters_json": "",
+ "filters_json": "[]",
+ "group_by_based_on": "designation",
+ "group_by_type": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 14:33:44.806626",
+ "modified": "2020-07-22 14:34:32.711881",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Designation Wise Openings",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/hr/dashboard_chart/gender_diversity_ratio/gender_diversity_ratio.json b/erpnext/hr/dashboard_chart/gender_diversity_ratio/gender_diversity_ratio.json
new file mode 100644
index 0000000..48578c9
--- /dev/null
+++ b/erpnext/hr/dashboard_chart/gender_diversity_ratio/gender_diversity_ratio.json
@@ -0,0 +1,29 @@
+{
+ "chart_name": "Gender Diversity Ratio",
+ "chart_type": "Group By",
+ "creation": "2020-07-22 11:56:32.667291",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Employee",
+ "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]",
+ "group_by_based_on": "gender",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 14:27:40.143783",
+ "modified": "2020-07-22 14:32:50.962459",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Gender Diversity Ratio",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Pie",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/hr/dashboard_chart/job_application_status/job_application_status.json b/erpnext/hr/dashboard_chart/job_application_status/job_application_status.json
new file mode 100644
index 0000000..42a8309
--- /dev/null
+++ b/erpnext/hr/dashboard_chart/job_application_status/job_application_status.json
@@ -0,0 +1,29 @@
+{
+ "chart_name": "Job Application Status",
+ "chart_type": "Group By",
+ "creation": "2020-07-22 11:56:32.699696",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Job Applicant",
+ "dynamic_filters_json": "",
+ "filters_json": "[[\"Job Applicant\",\"creation\",\"Timespan\",\"last month\",false]]",
+ "group_by_based_on": "status",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-28 16:19:12.109979",
+ "modified": "2020-07-28 16:19:45.279490",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Job Application Status",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Pie",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/hr/dashboard_fixtures.py b/erpnext/hr/dashboard_fixtures.py
deleted file mode 100644
index 6d8091b..0000000
--- a/erpnext/hr/dashboard_fixtures.py
+++ /dev/null
@@ -1,190 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-import erpnext
-import json
-from frappe import _
-
-def get_data():
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards(),
- })
-
-def get_dashboards():
- dashboards = []
- dashboards.append(get_human_resource_dashboard())
- return dashboards
-
-def get_human_resource_dashboard():
- return {
- "name": "Human Resource",
- "dashboard_name": "Human Resource",
- "is_default": 1,
- "charts": [
- { "chart": "Attendance Count", "width": "Full"},
- { "chart": "Gender Diversity Ratio", "width": "Half"},
- { "chart": "Job Application Status", "width": "Half"},
- { "chart": 'Designation Wise Employee Count', "width": "Half"},
- { "chart": 'Department Wise Employee Count', "width": "Half"},
- { "chart": 'Designation Wise Openings', "width": "Half"},
- { "chart": 'Department Wise Openings', "width": "Half"}
- ],
- "cards": [
- {"card": "Total Employees"},
- {"card": "New Joinees (Last year)"},
- {'card': "Employees Left (Last year)"},
- {'card': "Total Applicants (Last month)"},
- ]
- }
-
-def get_recruitment_dashboard():
- pass
-
-
-def get_charts():
- company = erpnext.get_default_company()
- date = frappe.utils.get_datetime()
-
- month_map = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov","Dec"]
-
-
- if not company:
- company = frappe.db.get_value("Company", {"is_group": 0}, "name")
-
- dashboard_charts = [
- get_dashboards_chart_doc('Gender Diversity Ratio', "Group By", "Pie",
- document_type = "Employee", group_by_type="Count", group_by_based_on="gender",
- filters_json = json.dumps([["Employee", "status", "=", "Active"]]))
- ]
-
- dashboard_charts.append(
- get_dashboards_chart_doc('Job Application Status', "Group By", "Pie",
- document_type = "Job Applicant", group_by_type="Count", group_by_based_on="status",
- filters_json = json.dumps([["Job Applicant", "creation", "Previous", "1 month"]]))
- )
-
- custom_options = '''{
- "type": "line",
- "axisOptions": {
- "shortenYAxisNumbers": 1
- },
- "tooltipOptions": {}
- }'''
-
- filters_json = json.dumps({
- "month": month_map[date.month - 1],
- "year": str(date.year),
- "company":company
- })
-
- dashboard_charts.append(
- get_dashboards_chart_doc('Attendance Count', "Report", "Line",
- report_name = "Monthly Attendance Sheet", is_custom =1, group_by_type="Count",
- filters_json = filters_json, custom_options=custom_options)
- )
-
- dashboard_charts.append(
- get_dashboards_chart_doc('Department Wise Employee Count', "Group By", "Donut",
- document_type = "Employee", group_by_type="Count", group_by_based_on="department",
- filters_json = json.dumps([["Employee", "status", "=", "Active"]]))
- )
-
- dashboard_charts.append(
- get_dashboards_chart_doc('Designation Wise Employee Count', "Group By", "Donut",
- document_type = "Employee", group_by_type="Count", group_by_based_on="designation",
- filters_json = json.dumps([["Employee", "status", "=", "Active"]]))
- )
-
- dashboard_charts.append(
- get_dashboards_chart_doc('Designation Wise Openings', "Group By", "Bar",
- document_type = "Job Opening", group_by_type="Sum", group_by_based_on="designation",
- time_interval = "Monthly", aggregate_function_based_on = "planned_vacancies")
- )
- dashboard_charts.append(
- get_dashboards_chart_doc('Department Wise Openings', "Group By", "Bar",
- document_type = "Job Opening", group_by_type="Sum", group_by_based_on="department",
- time_interval = "Monthly", aggregate_function_based_on = "planned_vacancies")
- )
- return dashboard_charts
-
-
-def get_number_cards():
- number_cards = []
-
- number_cards = [
- get_number_cards_doc("Employee", "Total Employees", filters_json = json.dumps([
- ["Employee","status","=","Active"]
- ])
- )
- ]
-
- number_cards.append(
- get_number_cards_doc("Employee", "New Joinees (Last year)", filters_json = json.dumps([
- ["Employee","date_of_joining","Previous","1 year"],
- ["Employee","status","=","Active"]
- ])
- )
- )
-
- number_cards.append(
- get_number_cards_doc("Employee", "Employees Left (Last year)", filters_json = json.dumps([
- ["Employee", "relieving_date", "Previous", "1 year"],
- ["Employee", "status", "=", "Left"]
- ])
- )
- )
-
- number_cards.append(
- get_number_cards_doc("Job Applicant", "Total Applicants (Last month)", filters_json = json.dumps([
- ["Job Applicant", "creation", "Previous", "1 month"]
- ])
- )
- )
-
- return number_cards
-
-
-def get_number_cards_doc(document_type, label, **args):
- args = frappe._dict(args)
-
- return {
- "doctype": "Number Card",
- "document_type": document_type,
- "function": args.func or "Count",
- "is_public": args.is_public or 1,
- "label": _(label),
- "name": args.name or label,
- "show_percentage_stats": args.show_percentage_stats or 1,
- "stats_time_interval": args.stats_time_interval or 'Monthly',
- "filters_json": args.filters_json or '[]',
- "aggregate_function_based_on": args.aggregate_function_based_on or None
- }
-
-def get_dashboards_chart_doc(name, chart_type, graph_type, **args):
- args = frappe._dict(args)
-
- return {
- "name": name,
- "chart_name": _(args.chart_name or name),
- "chart_type": chart_type,
- "document_type": args.document_type or None,
- "report_name": args.report_name or None,
- "is_custom": args.is_custom or 0,
- "group_by_type": args.group_by_type or None,
- "group_by_based_on": args.group_by_based_on or None,
- "based_on": args.based_on or None,
- "value_based_on": args.value_based_on or None,
- "number_of_groups": args.number_of_groups or 0,
- "is_public": args.is_public or 1,
- "timespan": args.timespan or "Last Year",
- "time_interval": args.time_interval or "Yearly",
- "timeseries": args.timeseries or 0,
- "filters_json": args.filters_json or '[]',
- "type": graph_type,
- "custom_options": args.custom_options or '',
- "doctype": "Dashboard Chart",
- "aggregate_function_based_on": args.aggregate_function_based_on or None
- }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py
index d4c118f..afd54b8 100644
--- a/erpnext/hr/doctype/department_approver/department_approver.py
+++ b/erpnext/hr/doctype/department_approver/department_approver.py
@@ -11,6 +11,7 @@
pass
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_approvers(doctype, txt, searchfield, start, page_len, filters):
if not filters.get("employee"):
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 7dacacf1..f2afe06 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -410,6 +410,8 @@
"options": "Branch"
},
{
+ "fetch_from": "grade.default_leave_policy",
+ "fetch_if_empty": 1,
"fieldname": "leave_policy",
"fieldtype": "Link",
"label": "Leave Policy",
@@ -804,16 +806,14 @@
"fieldname": "expense_approver",
"fieldtype": "Link",
"label": "Expense Approver",
- "options": "User",
- "show_days": 1,
- "show_seconds": 1
+ "options": "User"
}
],
"icon": "fa fa-user",
"idx": 24,
"image_field": "image",
"links": [],
- "modified": "2020-06-18 18:01:27.223535",
+ "modified": "2020-07-03 21:28:04.109189",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 4d49503..7338cbb 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -413,7 +413,11 @@
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False):
- filters = [['company', '=', company]]
+
+ filters = []
+ if company and company != 'All Companies':
+ filters = [['company', '=', company]]
+
fields = ['name as value', 'employee_name as title']
if is_root:
diff --git a/erpnext/hr/doctype/employee/employee_tree.js b/erpnext/hr/doctype/employee/employee_tree.js
index 0a2da63..9ab091a 100644
--- a/erpnext/hr/doctype/employee/employee_tree.js
+++ b/erpnext/hr/doctype/employee/employee_tree.js
@@ -4,7 +4,7 @@
{
fieldname: "company",
fieldtype:"Select",
- options: erpnext.utils.get_tree_options("company"),
+ options: ['All Companies'].concat(erpnext.utils.get_tree_options("company")),
label: __("Company"),
default: erpnext.utils.get_tree_default("company")
}
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 7619581..3c435b8 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -120,12 +120,14 @@
"reference_type": "Employee Advance",
"reference_name": doc.name,
"party_type": "Employee",
+ "cost_center": erpnext.get_default_cost_center(doc.company),
"party": doc.employee,
"is_advance": "Yes"
})
je.append("accounts", {
"account": payment_account.account,
+ "cost_center": erpnext.get_default_cost_center(doc.company),
"credit_in_account_currency": flt(doc.advance_amount),
"account_currency": payment_account.account_currency,
"account_type": payment_account.account_type
diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.json b/erpnext/hr/doctype/employee_checkin/employee_checkin.json
index 75f6997..d34316d 100644
--- a/erpnext/hr/doctype/employee_checkin/employee_checkin.json
+++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.json
@@ -41,8 +41,7 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Log Type",
- "options": "\nIN\nOUT",
- "reqd": 1
+ "options": "\nIN\nOUT"
},
{
"fieldname": "shift",
@@ -108,7 +107,7 @@
}
],
"links": [],
- "modified": "2020-01-23 04:57:42.551355",
+ "modified": "2020-07-08 11:02:32.660986",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Checkin",
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
index c128567..d6047e1 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
@@ -8,10 +8,20 @@
frm.add_fetch("employee_onboarding_template", "designation", "designation");
frm.add_fetch("employee_onboarding_template", "employee_grade", "employee_grade");
+
+ frm.set_query("job_applicant", function () {
+ return {
+ filters:{
+ "status": "Accepted",
+ }
+ };
+ });
+
frm.set_query('job_offer', function () {
return {
filters: {
- 'job_applicant': frm.doc.job_applicant
+ 'job_applicant': frm.doc.job_applicant,
+ 'docstatus': 1
}
};
});
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
index 3b95cab..783c757 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
@@ -1,620 +1,203 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "HR-EMP-ONB-.YYYY.-.#####",
- "beta": 0,
- "creation": "2018-05-09 04:57:20.016220",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "HR-EMP-ONB-.YYYY.-.#####",
+ "creation": "2018-05-09 04:57:20.016220",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "job_applicant",
+ "job_offer",
+ "employee_name",
+ "employee",
+ "date_of_joining",
+ "boarding_status",
+ "notify_users_by_email",
+ "column_break_7",
+ "employee_onboarding_template",
+ "company",
+ "department",
+ "designation",
+ "employee_grade",
+ "project",
+ "table_for_activity",
+ "activities",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "job_applicant",
- "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": "Job Applicant",
- "length": 0,
- "no_copy": 0,
- "options": "Job Applicant",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "job_offer",
- "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": "Job Offer",
- "length": 0,
- "no_copy": 0,
- "options": "Job Offer",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "job_applicant.applicant_name",
- "fieldname": "employee_name",
- "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": "Employee Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee",
- "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": "Employee",
- "length": 0,
- "no_copy": 0,
- "options": "Employee",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "date_of_joining",
- "fieldtype": "Date",
- "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": "Date of Joining",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "boarding_status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 0,
- "options": "\nPending\nIn Process\nCompleted",
- "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": "job_applicant",
+ "fieldtype": "Link",
+ "label": "Job Applicant",
+ "options": "Job Applicant",
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "notify_users_by_email",
- "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": "Notify users by email",
- "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": "job_offer",
+ "fieldtype": "Link",
+ "label": "Job Offer",
+ "options": "Job Offer",
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_7",
- "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
- },
+ "fetch_from": "job_applicant.applicant_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Employee Name",
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee_onboarding_template",
- "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": "Employee Onboarding Template",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Onboarding Template",
- "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": "employee",
+ "fieldtype": "Link",
+ "label": "Employee",
+ "options": "Employee",
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "date_of_joining",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Date of Joining",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "department",
- "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": "Department",
- "length": 0,
- "no_copy": 0,
- "options": "Department",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "fieldname": "boarding_status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "\nPending\nIn Process\nCompleted",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "designation",
- "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": "Designation",
- "length": 0,
- "no_copy": 0,
- "options": "Designation",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "notify_users_by_email",
+ "fieldtype": "Check",
+ "label": "Notify users by email",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee_grade",
- "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": "Employee Grade",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Grade",
- "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_7",
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "project",
- "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": "Project",
- "length": 0,
- "no_copy": 0,
- "options": "Project",
- "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": "employee_onboarding_template",
+ "fieldtype": "Link",
+ "label": "Employee Onboarding Template",
+ "options": "Employee Onboarding Template",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "table_for_activity",
- "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": "",
- "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": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "activities",
- "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": "Activities",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Boarding Activity",
- "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": "department",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Department",
+ "options": "Department",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "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": "Employee Onboarding",
- "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": "designation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Designation",
+ "options": "Designation",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "employee_grade",
+ "fieldtype": "Link",
+ "label": "Employee Grade",
+ "options": "Employee Grade",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project",
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "table_for_activity",
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "activities",
+ "fieldtype": "Table",
+ "label": "Activities",
+ "options": "Employee Boarding Activity",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Employee Onboarding",
+ "print_hide": 1,
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
}
- ],
- "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": "2019-08-01 16:15:55.968224",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Employee Onboarding",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-06-25 15:22:24.923835",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Onboarding",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "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
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "employee_name",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "employee_name",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
index 19ff3bd..6cc2bf5 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
@@ -13,6 +13,12 @@
class EmployeeOnboarding(EmployeeBoardingController):
def validate(self):
super(EmployeeOnboarding, self).validate()
+ self.validate_duplicate_employee_onboarding()
+
+ def validate_duplicate_employee_onboarding(self):
+ emp_onboarding = frappe.db.exists("Employee Onboarding",{"job_applicant": self.job_applicant})
+ if emp_onboarding and emp_onboarding != self.name:
+ frappe.throw(_("Employee Onboarding: {0} is already for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
def validate_employee_creation(self):
if self.docstatus != 1:
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
index 35c9f72..4e9ee3b 100644
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
@@ -8,6 +8,7 @@
from frappe.utils import nowdate
from erpnext.hr.doctype.employee_onboarding.employee_onboarding import make_employee
from erpnext.hr.doctype.employee_onboarding.employee_onboarding import IncompleteTaskError
+from erpnext.hr.doctype.job_offer.test_job_offer import create_job_offer
class TestEmployeeOnboarding(unittest.TestCase):
def test_employee_onboarding_incomplete_task(self):
@@ -15,8 +16,13 @@
frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
_set_up()
applicant = get_job_applicant()
+
+ job_offer = create_job_offer(job_applicant=applicant.name)
+ job_offer.submit()
+
onboarding = frappe.new_doc('Employee Onboarding')
onboarding.job_applicant = applicant.name
+ onboarding.job_offer = job_offer.name
onboarding.company = '_Test Company'
onboarding.designation = 'Researcher'
onboarding.append('activities', {
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index fa63ec2..221300b 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -113,6 +113,14 @@
cur_frm.cscript.calculate_total(doc,cdt,cdn);
};
+cur_frm.fields_dict['cost_center'].get_query = function(doc) {
+ return {
+ filters: {
+ "company": doc.company
+ }
+ }
+};
+
erpnext.expense_claim = {
set_title: function(frm) {
if (!frm.doc.task) {
@@ -300,6 +308,11 @@
cost_center: function(frm) {
frm.events.set_child_cost_center(frm);
},
+
+ validate: function(frm) {
+ frm.events.set_child_cost_center(frm);
+ },
+
set_child_cost_center: function(frm){
(frm.doc.expenses || []).forEach(function(d) {
if (!d.cost_center){
@@ -349,9 +362,6 @@
});
frappe.ui.form.on("Expense Claim Detail", {
- expenses_add: function(frm, cdt, cdn) {
- frm.events.set_child_cost_center(frm);
- },
amount: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
frappe.model.set_value(cdt, cdn, 'sanctioned_amount', child.amount);
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index ea469b8..bf893d5 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -129,7 +129,7 @@
"debit": data.sanctioned_amount,
"debit_in_account_currency": data.sanctioned_amount,
"against": self.employee,
- "cost_center": data.cost_center
+ "cost_center": data.cost_center or self.cost_center
}, item=data)
)
@@ -295,7 +295,7 @@
je = frappe.new_doc("Journal Entry")
je.voucher_type = 'Bank Entry'
je.company = expense_claim.company
- je.remark = 'Payment against Expense Claim: ' + dn;
+ je.remark = 'Payment against Expense Claim: ' + dn
je.append("accounts", {
"account": expense_claim.payable_account,
@@ -303,6 +303,7 @@
"reference_type": "Expense Claim",
"party_type": "Employee",
"party": expense_claim.employee,
+ "cost_center": erpnext.get_default_cost_center(expense_claim.company),
"reference_name": expense_claim.name
})
@@ -313,6 +314,7 @@
"reference_name": expense_claim.name,
"balance": default_bank_cash_account.balance,
"account_currency": default_bank_cash_account.account_currency,
+ "cost_center": erpnext.get_default_cost_center(expense_claim.company),
"account_type": default_bank_cash_account.account_type
})
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.js b/erpnext/hr/doctype/job_applicant/job_applicant.js
index 05071e1..c625155 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.js
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.js
@@ -10,10 +10,14 @@
refresh: function(frm) {
if (!frm.doc.__islocal) {
if (frm.doc.__onload && frm.doc.__onload.job_offer) {
+ $('[data-doctype="Employee Onboarding"]').find("button").show();
+ $('[data-doctype="Job Offer"]').find("button").hide();
frm.add_custom_button(__("Job Offer"), function() {
frappe.set_route("Form", "Job Offer", frm.doc.__onload.job_offer);
}, __("View"));
} else {
+ $('[data-doctype="Employee Onboarding"]').find("button").hide();
+ $('[data-doctype="Job Offer"]').find("button").show();
frm.add_custom_button(__("Job Offer"), function() {
frappe.route_options = {
"job_applicant": frm.doc.name,
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_list.js b/erpnext/hr/doctype/job_applicant/job_applicant_list.js
new file mode 100644
index 0000000..3b9141b
--- /dev/null
+++ b/erpnext/hr/doctype/job_applicant/job_applicant_list.js
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+// MIT License. See license.txt
+
+frappe.listview_settings['Job Applicant'] = {
+ add_fields: ["company", "designation", "job_applicant", "status"],
+ get_indicator: function (doc) {
+ if (doc.status == "Accepted") {
+ return [__(doc.status), "green", "status,=," + doc.status];
+ } else if (["Open", "Replied"].includes(doc.status)) {
+ return [__(doc.status), "orange", "status,=," + doc.status];
+ } else if (["Hold", "Rejected"].includes(doc.status)) {
+ return [__(doc.status), "red", "status,=," + doc.status];
+ }
+ }
+};
diff --git a/erpnext/hr/doctype/job_offer/job_offer.json b/erpnext/hr/doctype/job_offer/job_offer.json
index ccbfdc5..c0b7f69 100644
--- a/erpnext/hr/doctype/job_offer/job_offer.json
+++ b/erpnext/hr/doctype/job_offer/job_offer.json
@@ -30,7 +30,6 @@
{
"fieldname": "job_applicant",
"fieldtype": "Link",
- "in_list_view": 1,
"label": "Job Applicant",
"options": "Job Applicant",
"print_hide": 1,
@@ -161,7 +160,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2019-12-31 02:40:33.650728",
+ "modified": "2020-06-25 00:56:24.756395",
"modified_by": "Administrator",
"module": "HR",
"name": "Job Offer",
diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py
index f9ee44a..e7e1a37 100644
--- a/erpnext/hr/doctype/job_offer/job_offer.py
+++ b/erpnext/hr/doctype/job_offer/job_offer.py
@@ -15,6 +15,9 @@
def validate(self):
self.validate_vacancies()
+ job_offer = frappe.db.exists("Job Offer",{"job_applicant": self.job_applicant})
+ if job_offer and job_offer != self.name:
+ frappe.throw(_("Job Offer: {0} is already for Job Applicant: {1}").format(frappe.bold(job_offer), frappe.bold(self.job_applicant)))
def validate_vacancies(self):
staffing_plan = get_staffing_plan_detail(self.designation, self.company, self.offer_date)
diff --git a/erpnext/hr/doctype/job_offer/job_offer_list.js b/erpnext/hr/doctype/job_offer/job_offer_list.js
new file mode 100644
index 0000000..4fa5be7
--- /dev/null
+++ b/erpnext/hr/doctype/job_offer/job_offer_list.js
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+// MIT License. See license.txt
+
+frappe.listview_settings['Job Offer'] = {
+ add_fields: ["company", "designation", "job_applicant", "status"],
+ get_indicator: function (doc) {
+ if (doc.status == "Accepted") {
+ return [__(doc.status), "green", "status,=," + doc.status];
+ } else if (doc.status == "Awaiting Response") {
+ return [__(doc.status), "orange", "status,=," + doc.status];
+ } else if (doc.status == "Rejected") {
+ return [__(doc.status), "red", "status,=," + doc.status];
+ }
+ }
+};
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index fb1f2c0..4001a45 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -40,6 +40,8 @@
validate: function(frm) {
if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1){
frm.doc.half_day_date = frm.doc.from_date;
+ }else if (frm.doc.half_day == 0){
+ frm.doc.half_day_date = "";
}
frm.toggle_reqd("half_day_date", frm.doc.half_day == 1);
},
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 0423824..3f25f58 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -293,6 +293,8 @@
def set_half_day_date(self):
if self.from_date == self.to_date and self.half_day == 1:
self.half_day_date = self.from_date
+ elif self.half_day == 0:
+ self.half_day_date = None
def notify_employee(self):
employee = frappe.get_doc("Employee", self.employee)
diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
index 48a2045..ff5dc2f 100644
--- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
+++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
@@ -1,4 +1,5 @@
from __future__ import unicode_literals
+from frappe import _
def get_data():
return {
@@ -8,13 +9,17 @@
},
'transactions': [
{
- 'items': ['Employee']
+ 'label': _('Employees'),
+ 'items': ['Employee', 'Employee Grade']
},
{
- 'items': ['Employee Grade']
- },
- {
+ 'label': _('Leaves'),
'items': ['Leave Allocation']
},
]
- }
\ No newline at end of file
+ }
+
+
+
+
+
\ No newline at end of file
diff --git a/erpnext/hr/doctype/vehicle/vehicle.py b/erpnext/hr/doctype/vehicle/vehicle.py
index a75cfa6..1df5068 100644
--- a/erpnext/hr/doctype/vehicle/vehicle.py
+++ b/erpnext/hr/doctype/vehicle/vehicle.py
@@ -13,4 +13,11 @@
if getdate(self.start_date) > getdate(self.end_date):
frappe.throw(_("Insurance Start date should be less than Insurance End date"))
if getdate(self.carbon_check_date) > getdate():
- frappe.throw(_("Last carbon check date cannot be a future date"))
\ No newline at end of file
+ frappe.throw(_("Last carbon check date cannot be a future date"))
+
+def get_timeline_data(doctype, name):
+ '''Return timeline for vehicle log'''
+ return dict(frappe.db.sql('''select unix_timestamp(date), count(*)
+ from `tabVehicle Log` where license_plate=%s
+ and date > date_sub(curdate(), interval 1 year)
+ group by date''', name))
diff --git a/erpnext/hr/hr_dashboard/human_resource/human_resource.json b/erpnext/hr/hr_dashboard/human_resource/human_resource.json
new file mode 100644
index 0000000..f74d9a3
--- /dev/null
+++ b/erpnext/hr/hr_dashboard/human_resource/human_resource.json
@@ -0,0 +1,58 @@
+{
+ "cards": [
+ {
+ "card": "Total Employees"
+ },
+ {
+ "card": "New Joinees (Last year)"
+ },
+ {
+ "card": "Employees Left (Last year)"
+ },
+ {
+ "card": "Total Applicants (Last month)"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "Attendance Count",
+ "width": "Full"
+ },
+ {
+ "chart": "Gender Diversity Ratio",
+ "width": "Half"
+ },
+ {
+ "chart": "Job Application Status",
+ "width": "Half"
+ },
+ {
+ "chart": "Designation Wise Employee Count",
+ "width": "Half"
+ },
+ {
+ "chart": "Department Wise Employee Count",
+ "width": "Half"
+ },
+ {
+ "chart": "Designation Wise Openings",
+ "width": "Half"
+ },
+ {
+ "chart": "Department Wise Openings",
+ "width": "Half"
+ }
+ ],
+ "creation": "2020-07-22 11:56:33.015888",
+ "dashboard_name": "Human Resource",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 0,
+ "is_standard": 1,
+ "modified": "2020-07-22 14:42:12.789249",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Human Resource",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/hr/module_onboarding/human_resource/human_resource.json b/erpnext/hr/module_onboarding/human_resource/human_resource.json
index e64582b..518c002 100644
--- a/erpnext/hr/module_onboarding/human_resource/human_resource.json
+++ b/erpnext/hr/module_onboarding/human_resource/human_resource.json
@@ -13,7 +13,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/human-resources",
"idx": 0,
"is_complete": 0,
- "modified": "2020-05-20 11:20:07.992597",
+ "modified": "2020-07-08 14:05:47.018799",
"modified_by": "Administrator",
"module": "HR",
"name": "Human Resource",
@@ -44,8 +44,7 @@
"step": "HR Settings"
}
],
- "subtitle": "Employee, Leaves and more.",
- "success_message": "The HR Module is all set up!",
- "title": "Let's Setup the Human Resource Module. ",
- "user_can_dismiss": 0
+ "subtitle": "Employee, Leaves, and more.",
+ "success_message": "The Human Resource Module is all set up!",
+ "title": "Let's Set Up the Human Resource Module. "
}
\ No newline at end of file
diff --git "a/erpnext/hr/number_card/employees_left_\050last_year\051/employees_left_\050last_year\051.json" "b/erpnext/hr/number_card/employees_left_\050last_year\051/employees_left_\050last_year\051.json"
new file mode 100644
index 0000000..6a91912
--- /dev/null
+++ "b/erpnext/hr/number_card/employees_left_\050last_year\051/employees_left_\050last_year\051.json"
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-22 11:56:32.947790",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Employee",
+ "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Employee\",\"relieving_date\",\"Timespan\",\"last year\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Employees Left (Last year)",
+ "modified": "2020-07-23 12:03:26.747447",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employees Left (Last year)",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git "a/erpnext/hr/number_card/new_joinees_\050last_year\051/new_joinees_\050last_year\051.json" "b/erpnext/hr/number_card/new_joinees_\050last_year\051/new_joinees_\050last_year\051.json"
new file mode 100644
index 0000000..8f5ad9c
--- /dev/null
+++ "b/erpnext/hr/number_card/new_joinees_\050last_year\051/new_joinees_\050last_year\051.json"
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-22 11:56:32.914057",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Employee",
+ "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Employee\",\"date_of_joining\",\"Timespan\",\"last year\",false],[\"Employee\",\"status\",\"=\",\"Active\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "New Joinees (Last year)",
+ "modified": "2020-07-22 14:32:09.352301",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "New Joinees (Last year)",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git "a/erpnext/hr/number_card/total_applicants_\050last_month\051/total_applicants_\050last_month\051.json" "b/erpnext/hr/number_card/total_applicants_\050last_month\051/total_applicants_\050last_month\051.json"
new file mode 100644
index 0000000..1af42ca
--- /dev/null
+++ "b/erpnext/hr/number_card/total_applicants_\050last_month\051/total_applicants_\050last_month\051.json"
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-22 11:56:32.977716",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Job Applicant",
+ "dynamic_filters_json": "",
+ "filters_json": "[[\"Job Applicant\",\"creation\",\"Timespan\",\"last month\"]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Applicants (Last month)",
+ "modified": "2020-07-22 14:32:27.656855",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Total Applicants (Last month)",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/hr/number_card/total_employees/total_employees.json b/erpnext/hr/number_card/total_employees/total_employees.json
new file mode 100644
index 0000000..932e255
--- /dev/null
+++ b/erpnext/hr/number_card/total_employees/total_employees.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-22 11:56:32.874849",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Employee",
+ "dynamic_filters_json": "[[\"Employee\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Employee\",\"status\",\"=\",\"Active\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Employees",
+ "modified": "2020-07-22 14:31:59.118650",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Total Employees",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js
index bd4ed3c..42f7cdb 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js
+++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js
@@ -5,12 +5,25 @@
frappe.query_reports["Monthly Attendance Sheet"] = {
"filters": [
{
- "fieldname":"month",
+ "fieldname": "month",
"label": __("Month"),
"fieldtype": "Select",
- "options": "Jan\nFeb\nMar\nApr\nMay\nJun\nJul\nAug\nSep\nOct\nNov\nDec",
- "default": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
- "Dec"][frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth()],
+ "reqd": 1 ,
+ "options": [
+ { "value": 1, "label": __("Jan") },
+ { "value": 2, "label": __("Feb") },
+ { "value": 3, "label": __("Mar") },
+ { "value": 4, "label": __("Apr") },
+ { "value": 5, "label": __("May") },
+ { "value": 6, "label": __("June") },
+ { "value": 7, "label": __("July") },
+ { "value": 8, "label": __("Aug") },
+ { "value": 9, "label": __("Sep") },
+ { "value": 10, "label": __("Oct") },
+ { "value": 11, "label": __("Nov") },
+ { "value": 12, "label": __("Dec") },
+ ],
+ "default": frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth() + 1
},
{
"fieldname":"year",
@@ -22,7 +35,15 @@
"fieldname":"employee",
"label": __("Employee"),
"fieldtype": "Link",
- "options": "Employee"
+ "options": "Employee",
+ get_query: () => {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: {
+ 'company': company
+ }
+ };
+ }
},
{
"fieldname":"company",
diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
index 47daab1..4608212 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
@@ -248,10 +248,7 @@
if not (filters.get("month") and filters.get("year")):
msgprint(_("Please select month and year"), raise_exception=1)
- filters["month"] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
- "Dec"].index(filters.month) + 1
-
- filters["total_days_in_month"] = monthrange(cint(filters.year), filters.month)[1]
+ filters["total_days_in_month"] = monthrange(cint(filters.year), cint(filters.month))[1]
conditions = " and month(attendance_date) = %(month)s and year(attendance_date) = %(year)s"
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/hr/report/recruitment_analytics/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/hr/report/recruitment_analytics/__init__.py
diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.js b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.js
new file mode 100644
index 0000000..9620f52
--- /dev/null
+++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Recruitment Analytics"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"on_date",
+ "label": __("On Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.now_date(),
+ "reqd": 1,
+ },
+ ]
+};
\ No newline at end of file
diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.json b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.json
new file mode 100644
index 0000000..30a8e17
--- /dev/null
+++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.json
@@ -0,0 +1,27 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-05-14 16:28:45.743869",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-05-14 16:28:45.743869",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Recruitment Analytics",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Staffing Plan",
+ "report_name": "Recruitment Analytics",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "HR Manager"
+ },
+ {
+ "role": "HR User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
new file mode 100644
index 0000000..8672094
--- /dev/null
+++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
@@ -0,0 +1,188 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+
+ if not filters: filters = {}
+ filters = frappe._dict(filters)
+
+ columns = get_columns()
+
+ data = get_data(filters)
+
+ return columns, data
+
+
+def get_columns():
+ return [
+ {
+ "label": _("Staffing Plan"),
+ "fieldtype": "Link",
+ "fieldname": "staffing_plan",
+ "options": "Staffing Plan",
+ "width": 150
+ },
+ {
+ "label": _("Job Opening"),
+ "fieldtype": "Link",
+ "fieldname": "job_opening",
+ "options": "Job Opening",
+ "width": 100
+ },
+ {
+ "label": _("Job Applicant"),
+ "fieldtype": "Link",
+ "fieldname": "job_applicant",
+ "options": "Job Applicant",
+ "width": 150
+ },
+ {
+ "label": _("Applicant name"),
+ "fieldtype": "data",
+ "fieldname": "applicant_name",
+ "width": 120
+ },
+ {
+ "label": _("Application Status"),
+ "fieldtype": "Data",
+ "fieldname": "application_status",
+ "width": 100
+ },
+ {
+ "label": _("Job Offer"),
+ "fieldtype": "Link",
+ "fieldname": "job_offer",
+ "options": "job Offer",
+ "width": 150
+ },
+ {
+ "label": _("Designation"),
+ "fieldtype": "Data",
+ "fieldname": "designation",
+ "width": 100
+ },
+ {
+ "label": _("Offer Date"),
+ "fieldtype": "date",
+ "fieldname": "offer_date",
+ "width": 100
+ },
+ {
+ "label": _("Job Offer status"),
+ "fieldtype": "Data",
+ "fieldname": "job_offer_status",
+ "width": 150
+ }
+ ]
+
+def get_data(filters):
+ data = []
+ staffing_plan_details = get_staffing_plan(filters)
+ staffing_plan_list = list(set([details["name"] for details in staffing_plan_details]))
+ sp_jo_map , jo_list = get_job_opening(staffing_plan_list)
+ jo_ja_map , ja_list = get_job_applicant(jo_list)
+ ja_joff_map = get_job_offer(ja_list)
+
+ for sp in sp_jo_map.keys():
+ parent_row = get_parent_row(sp_jo_map, sp, jo_ja_map, ja_joff_map)
+ data += parent_row
+
+ return data
+
+
+def get_parent_row(sp_jo_map, sp, jo_ja_map, ja_joff_map):
+ data = []
+ for jo in sp_jo_map[sp]:
+ row = {
+ "staffing_plan" : sp,
+ "job_opening" : jo["name"],
+ }
+ data.append(row)
+ child_row = get_child_row( jo["name"], jo_ja_map, ja_joff_map)
+ data += child_row
+ return data
+
+def get_child_row(jo, jo_ja_map, ja_joff_map):
+ data = []
+ for ja in jo_ja_map[jo]:
+ row = {
+ "indent":1,
+ "job_applicant": ja.name,
+ "applicant_name": ja.applicant_name,
+ "application_status": ja.status,
+ }
+ if ja.name in ja_joff_map.keys():
+ jo_detail =ja_joff_map[ja.name][0]
+ row["job_offer"] = jo_detail.name
+ row["job_offer_status"] = jo_detail.status
+ row["offer_date"]= jo_detail.offer_date.strftime("%d-%m-%Y")
+ row["designation"] = jo_detail.designation
+
+ data.append(row)
+ return data
+
+def get_staffing_plan(filters):
+
+ staffing_plan = frappe.db.sql("""
+ select
+ sp.name, sp.department, spd.designation, spd.vacancies, spd.current_count, spd.parent, sp.to_date
+ from
+ `tabStaffing Plan Detail` spd , `tabStaffing Plan` sp
+ where
+ spd.parent = sp.name
+ And
+ sp.to_date > '{0}'
+ """.format(filters.on_date), as_dict = 1)
+
+ return staffing_plan
+
+def get_job_opening(sp_list):
+
+ job_openings = frappe.get_all("Job Opening", filters = [["staffing_plan", "IN", sp_list]], fields =["name", "staffing_plan"])
+
+ sp_jo_map = {}
+ jo_list = []
+
+ for openings in job_openings:
+ if openings.staffing_plan not in sp_jo_map.keys():
+ sp_jo_map[openings.staffing_plan] = [openings]
+ else:
+ sp_jo_map[openings.staffing_plan].append(openings)
+
+ jo_list.append(openings.name)
+
+ return sp_jo_map, jo_list
+
+def get_job_applicant(jo_list):
+
+ jo_ja_map = {}
+ ja_list =[]
+
+ applicants = frappe.get_all("Job Applicant", filters = [["job_title", "IN", jo_list]], fields =["name", "job_title","applicant_name", 'status'])
+
+ for applicant in applicants:
+ if applicant.job_title not in jo_ja_map.keys():
+ jo_ja_map[applicant.job_title] = [applicant]
+ else:
+ jo_ja_map[applicant.job_title].append(applicant)
+
+ ja_list.append(applicant.name)
+
+ return jo_ja_map , ja_list
+
+def get_job_offer(ja_list):
+ ja_joff_map = {}
+
+ offers = frappe.get_all("Job offer", filters = [["job_applicant", "IN", ja_list]], fields =["name", "job_applicant", "status", 'offer_date', 'designation'])
+
+ for offer in offers:
+ if offer.job_applicant not in ja_joff_map.keys():
+ ja_joff_map[offer.job_applicant] = [offer]
+ else:
+ ja_joff_map[offer.job_applicant].append(offer)
+
+ return ja_joff_map
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js
index 9cd8b2e..ffef60b 100644
--- a/erpnext/loan_management/doctype/loan/loan.js
+++ b/erpnext/loan_management/doctype/loan/loan.js
@@ -45,15 +45,6 @@
});
})
- frm.set_query('loan_security_pledge', function(doc, cdt, cdn) {
- return {
- filters: {
- applicant: frm.doc.applicant,
- docstatus: 1,
- loan_application: frm.doc.loan_application || ''
- }
- };
- });
},
refresh: function (frm) {
@@ -86,9 +77,6 @@
frm.toggle_display("repayment_periods", s1 - frm.doc.is_term_loan);
},
- is_secured_loan: function(frm) {
- frm.toggle_reqd("loan_security_pledge", frm.doc.is_secured_loan);
- },
make_loan_disbursement: function (frm) {
frappe.call({
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index b04e822..192beee 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -25,15 +25,12 @@
"disbursement_date",
"disbursed_amount",
"column_break_11",
+ "maximum_loan_amount",
"is_term_loan",
"repayment_method",
"repayment_periods",
"monthly_repayment_amount",
"repayment_start_date",
- "loan_security_details_section",
- "loan_security_pledge",
- "column_break_25",
- "maximum_loan_value",
"account_info",
"mode_of_payment",
"payment_account",
@@ -292,13 +289,8 @@
"default": "0",
"fieldname": "is_secured_loan",
"fieldtype": "Check",
- "label": "Is Secured Loan"
- },
- {
- "depends_on": "is_secured_loan",
- "fieldname": "loan_security_details_section",
- "fieldtype": "Section Break",
- "label": "Loan Security Details"
+ "label": "Is Secured Loan",
+ "read_only": 1
},
{
"default": "0",
@@ -325,12 +317,6 @@
"read_only": 1
},
{
- "fieldname": "loan_security_pledge",
- "fieldtype": "Link",
- "label": "Loan Security Pledge",
- "options": "Loan Security Pledge"
- },
- {
"fieldname": "disbursed_amount",
"fieldtype": "Currency",
"label": "Disbursed Amount",
@@ -338,21 +324,17 @@
"read_only": 1
},
{
- "fetch_from": "loan_security_pledge.maximum_loan_value",
- "fieldname": "maximum_loan_value",
+ "fetch_from": "loan_application.maximum_loan_amount",
+ "fieldname": "maximum_loan_amount",
"fieldtype": "Currency",
- "label": "Maximum Loan Value",
+ "label": "Maximum Loan Amount",
"options": "Company:company:default_currency",
"read_only": 1
- },
- {
- "fieldname": "column_break_25",
- "fieldtype": "Column Break"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-13 13:16:10.192624",
+ "modified": "2020-07-02 20:46:40.128142",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 76e10e5..e20b484 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -13,11 +13,9 @@
class Loan(AccountsController):
def validate(self):
self.set_loan_amount()
-
+ self.validate_loan_amount()
self.set_missing_fields()
self.validate_accounts()
- self.validate_loan_security_pledge()
- self.validate_loan_amount()
self.check_sanctioned_amount_limit()
self.validate_repay_from_salary()
@@ -56,21 +54,6 @@
if self.repayment_method == "Repay Over Number of Periods":
self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
- def validate_loan_security_pledge(self):
-
- if self.is_secured_loan and not self.loan_security_pledge:
- frappe.throw(_("Loan Security Pledge is mandatory for secured loan"))
-
- if self.loan_security_pledge:
- loan_security_details = frappe.db.get_value("Loan Security Pledge", self.loan_security_pledge,
- ['loan', 'company'], as_dict=1)
-
- if loan_security_details.loan:
- frappe.throw(_("Loan Security Pledge already pledged against loan {0}").format(loan_security_details.loan))
-
- if loan_security_details.company != self.company:
- frappe.throw(_("Loan Security Pledge Company and Loan Company must be same"))
-
def check_sanctioned_amount_limit(self):
total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
@@ -129,22 +112,29 @@
self.total_payment = self.loan_amount
def set_loan_amount(self):
+ if self.loan_application and not self.loan_amount:
+ self.loan_amount = frappe.db.get_value('Loan Application', self.loan_application, 'loan_amount')
- if not self.loan_amount and self.is_secured_loan and self.loan_security_pledge:
- self.loan_amount = self.maximum_loan_value
def validate_loan_amount(self):
- if self.is_secured_loan and self.loan_amount > self.maximum_loan_value:
- msg = _("Loan amount cannot be greater than {0}").format(self.maximum_loan_value)
+ if self.maximum_loan_amount and self.loan_amount > self.maximum_loan_amount:
+ msg = _("Loan amount cannot be greater than {0}").format(self.maximum_loan_amount)
frappe.throw(msg)
if not self.loan_amount:
frappe.throw(_("Loan amount is mandatory"))
def link_loan_security_pledge(self):
- frappe.db.sql("""UPDATE `tabLoan Security Pledge` SET
- loan = %s, status = 'Pledged', pledge_time = %s
- where name = %s """, (self.name, now_datetime(), self.loan_security_pledge))
+ if self.is_secured_loan:
+ loan_security_pledge = frappe.db.get_value('Loan Security Pledge', {'loan_application': self.loan_application},
+ 'name')
+
+ if loan_security_pledge:
+ frappe.db.set_value('Loan Security Pledge', loan_security_pledge, {
+ 'loan': self.name,
+ 'status': 'Pledged',
+ 'pledge_time': now_datetime()
+ })
def unlink_loan_security_pledge(self):
frappe.db.sql("""UPDATE `tabLoan Security Pledge` SET
@@ -235,8 +225,10 @@
@frappe.whitelist()
def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_dict=1):
loan_security_pledge_details = frappe.db.sql("""
- SELECT p.parent, p.loan_security, p.qty as qty FROM `tabLoan Security Pledge` lsp , `tabPledge` p
+ SELECT p.loan_security, sum(p.qty) as qty
+ FROM `tabLoan Security Pledge` lsp , `tabPledge` p
WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1
+ GROUP BY p.loan_security
""",(loan), as_dict=1)
unpledge_request = frappe.new_doc("Loan Security Unpledge")
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 3f37a26..c65996e 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -16,6 +16,7 @@
from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall
from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
+from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
class TestLoan(unittest.TestCase):
def setUp(self):
@@ -72,31 +73,31 @@
self.assertEquals(loan.total_payment, 302712)
def test_loan_with_security(self):
- pledges = []
- pledges.append({
+
+ pledge = [{
"loan_security": "Test Security 1",
"qty": 4000.00,
- "haircut": 50,
- "loan_security_price": 500.00
- })
+ }]
- loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
+ loan_application = create_loan_application('_Test Company', self.applicant2,
+ 'Stock Loan', pledge, "Repay Over Number of Periods", 12)
+ create_pledge(loan_application)
- loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_security_pledge.name)
-
+ loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods",
+ 12, loan_application)
self.assertEquals(loan.loan_amount, 1000000)
def test_loan_disbursement(self):
- pledges = []
- pledges.append({
+ pledge = [{
"loan_security": "Test Security 1",
- "qty": 4000.00,
- "haircut": 50
- })
+ "qty": 4000.00
+ }]
- loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
+ loan_application = create_loan_application('_Test Company', self.applicant2, 'Stock Loan', pledge, "Repay Over Number of Periods", 12)
- loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_security_pledge.name)
+ create_pledge(loan_application)
+
+ loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
self.assertEquals(loan.loan_amount, 1000000)
loan.submit()
@@ -121,18 +122,15 @@
self.assertTrue(gl_entries2)
def test_regular_loan_repayment(self):
- pledges = []
- pledges.append({
+ pledge = [{
"loan_security": "Test Security 1",
- "qty": 4000.00,
- "haircut": 50
- })
+ "qty": 4000.00
+ }]
- loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
+ loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ create_pledge(loan_application)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name,
- posting_date=get_first_day(nowdate()))
-
+ loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
loan.submit()
self.assertEquals(loan.loan_amount, 1000000)
@@ -166,16 +164,15 @@
penalty_amount - amounts[0], 2))
def test_loan_closure_repayment(self):
- pledges = []
- pledges.append({
+ pledge = [{
"loan_security": "Test Security 1",
- "qty": 4000.00,
- "haircut": 50
- })
+ "qty": 4000.00
+ }]
- loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name,
- posting_date=get_first_day(nowdate()))
+ loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ create_pledge(loan_application)
+
+ loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
loan.submit()
self.assertEquals(loan.loan_amount, 1000000)
@@ -214,23 +211,21 @@
self.assertEquals(loan.status, "Loan Closure Requested")
def test_loan_repayment_for_term_loan(self):
- pledges = []
- pledges.append({
+ pledges = [{
"loan_security": "Test Security 2",
- "qty": 4000.00,
- "haircut": 50
- })
-
- pledges.append({
+ "qty": 4000.00
+ },
+ {
"loan_security": "Test Security 1",
- "qty": 2000.00,
- "haircut": 50
- })
+ "qty": 2000.00
+ }]
- loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
+ loan_application = create_loan_application('_Test Company', self.applicant2, 'Stock Loan', pledges,
+ "Repay Over Number of Periods", 12)
+ create_pledge(loan_application)
- loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12,
- loan_security_pledge.name, posting_date=add_months(nowdate(), -1))
+ loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application,
+ posting_date=add_months(nowdate(), -1))
loan.submit()
@@ -250,16 +245,18 @@
self.assertEquals(amounts[1], 78303.00)
def test_security_shortfall(self):
- pledges = []
- pledges.append({
+ pledges = [{
"loan_security": "Test Security 2",
"qty": 8000.00,
"haircut": 50,
- })
+ }]
- loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
+ loan_application = create_loan_application('_Test Company', self.applicant2,
+ 'Stock Loan', pledges, "Repay Over Number of Periods", 12)
- loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_security_pledge.name)
+ create_pledge(loan_application)
+
+ loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
loan.submit()
make_loan_disbursement_entry(loan.name, loan.loan_amount)
@@ -279,16 +276,15 @@
where loan_security='Test Security 2'""")
def test_loan_security_unpledge(self):
- pledges = []
- pledges.append({
+ pledge = [{
"loan_security": "Test Security 1",
- "qty": 4000.00,
- "haircut": 50
- })
+ "qty": 4000.00
+ }]
- loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
- loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name,
- posting_date=get_first_day(nowdate()))
+ loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ create_pledge(loan_application)
+
+ loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
loan.submit()
self.assertEquals(loan.loan_amount, 1000000)
@@ -446,12 +442,13 @@
"haircut": 50.00,
}).insert(ignore_permissions=True)
-def create_loan_security_pledge(applicant, pledges):
+def create_loan_security_pledge(applicant, pledges, loan_application):
lsp = frappe.new_doc("Loan Security Pledge")
lsp.applicant_type = 'Customer'
lsp.applicant = applicant
lsp.company = "_Test Company"
+ lsp.loan_application = loan_application
for pledge in pledges:
lsp.append('securities', {
@@ -510,6 +507,31 @@
return lr
+def create_loan_application(company, applicant, loan_type, proposed_pledges, repayment_method=None,
+ repayment_periods=None, posting_date=None):
+ loan_application = frappe.new_doc('Loan Application')
+ loan_application.applicant_type = 'Customer'
+ loan_application.company = company
+ loan_application.applicant = applicant
+ loan_application.loan_type = loan_type
+ loan_application.posting_date = posting_date or nowdate()
+ loan_application.is_secured_loan = 1
+
+ if repayment_method:
+ loan_application.repayment_method = repayment_method
+ loan_application.repayment_periods = repayment_periods
+
+ for pledge in proposed_pledges:
+ loan_application.append('proposed_pledges', pledge)
+
+ loan_application.save()
+ loan_application.submit()
+
+ loan_application.status = 'Approved'
+ loan_application.save()
+
+ return loan_application.name
+
def create_loan(applicant, loan_type, loan_amount, repayment_method, repayment_periods,
repayment_start_date=None, posting_date=None):
@@ -531,14 +553,13 @@
loan.save()
return loan
-def create_loan_with_security(applicant, loan_type, repayment_method, repayment_periods, loan_security_pledge,
- posting_date=None, repayment_start_date=None):
-
+def create_loan_with_security(applicant, loan_type, repayment_method, repayment_periods, loan_application, posting_date=None, repayment_start_date=None):
loan = frappe.get_doc({
"doctype": "Loan",
"company": "_Test Company",
"applicant_type": "Customer",
"posting_date": posting_date or nowdate(),
+ "loan_application": loan_application,
"applicant": applicant,
"loan_type": loan_type,
"is_term_loan": 1,
@@ -547,7 +568,6 @@
"repayment_periods": repayment_periods,
"repayment_start_date": repayment_start_date or nowdate(),
"mode_of_payment": frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'),
- "loan_security_pledge": loan_security_pledge,
"payment_account": 'Payment Account - _TC',
"loan_account": 'Loan Account - _TC',
"interest_income_account": 'Interest Income Account - _TC',
@@ -558,19 +578,19 @@
return loan
-def create_demand_loan(applicant, loan_type, loan_security_pledge, posting_date=None):
+def create_demand_loan(applicant, loan_type, loan_application, posting_date=None):
loan = frappe.get_doc({
"doctype": "Loan",
"company": "_Test Company",
"applicant_type": "Customer",
"posting_date": posting_date or nowdate(),
+ 'loan_application': loan_application,
"applicant": applicant,
"loan_type": loan_type,
"is_term_loan": 0,
"is_secured_loan": 1,
"mode_of_payment": frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'),
- "loan_security_pledge": loan_security_pledge,
"payment_account": 'Payment Account - _TC',
"loan_account": 'Loan Account - _TC',
"interest_income_account": 'Interest Income Account - _TC',
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py
index d3b8164..f051755 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.py
@@ -103,10 +103,13 @@
if self.is_secured_loan and not self.proposed_pledges:
frappe.throw(_("Proposed Pledges are mandatory for secured Loans"))
- if not self.loan_amount and self.is_secured_loan and self.proposed_pledges:
- self.loan_amount = 0
+ if self.is_secured_loan and self.proposed_pledges:
+ self.maximum_loan_amount = 0
for security in self.proposed_pledges:
- self.loan_amount += security.post_haircut_amount
+ self.maximum_loan_amount += security.post_haircut_amount
+
+ if not self.loan_amount and self.is_secured_loan and self.proposed_pledges:
+ self.loan_amount = self.maximum_loan_amount
@frappe.whitelist()
def create_loan(source_name, target_doc=None, submit=0):
@@ -116,7 +119,6 @@
filters = {'name': source_doc.loan_type}
)[0]
- loan_security_pledge = frappe.db.get_value("Loan Security Pledge", {"loan_application": source_name}, 'name')
target_doc.mode_of_payment = account_details.mode_of_payment
target_doc.payment_account = account_details.payment_account
@@ -124,9 +126,6 @@
target_doc.interest_income_account = account_details.interest_income_account
target_doc.penalty_income_account = account_details.penalty_income_account
- if loan_security_pledge:
- target_doc.is_secured_loan = 1
- target_doc.loan_security_pledge = loan_security_pledge
doclist = get_mapped_doc("Loan Application", source_name, {
"Loan Application": {
diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
index 0c1578f..2cb2637 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
@@ -5,11 +5,12 @@
import frappe
import unittest
from frappe.utils import (nowdate, add_days, get_datetime, get_first_day, get_last_day, date_diff, flt, add_to_date)
-from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_pledge, create_repayment_entry,
+from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_pledge, create_repayment_entry, create_loan_application,
make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan, create_loan_security_price)
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
from erpnext.selling.doctype.customer.test_customer import get_customer_dict
+from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
class TestLoanDisbursement(unittest.TestCase):
@@ -31,18 +32,15 @@
self.applicant = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name')
def test_loan_topup(self):
- pledges = []
- pledges.append({
+ pledge = [{
"loan_security": "Test Security 1",
- "qty": 4000.00,
- "haircut": 50,
- "loan_security_price": 500.00
- })
+ "qty": 4000.00
+ }]
- loan_security_pledge = create_loan_security_pledge(self.applicant, pledges)
+ loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
+ create_pledge(loan_application)
- loan = create_demand_loan(self.applicant, "Demand Loan", loan_security_pledge.name,
- posting_date=get_first_day(nowdate()))
+ loan = create_demand_loan(self.applicant, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
loan.submit()
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
index 2afed08..4b85b21 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
@@ -6,10 +6,11 @@
import unittest
from frappe.utils import (nowdate, add_days, get_datetime, get_first_day, get_last_day, date_diff, flt, add_to_date)
from erpnext.loan_management.doctype.loan.test_loan import (create_loan_type, create_loan_security_pledge, create_loan_security_price,
- make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan)
+ make_loan_disbursement_entry, create_loan_accounts, create_loan_security_type, create_loan_security, create_demand_loan, create_loan_application)
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
from erpnext.selling.doctype.customer.test_customer import get_customer_dict
+from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
class TestLoanInterestAccrual(unittest.TestCase):
def setUp(self):
@@ -29,17 +30,15 @@
self.applicant = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name')
def test_loan_interest_accural(self):
- pledges = []
- pledges.append({
+ pledge = [{
"loan_security": "Test Security 1",
- "qty": 4000.00,
- "haircut": 50,
- "loan_security_price": 500.00
- })
+ "qty": 4000.00
+ }]
- loan_security_pledge = create_loan_security_pledge(self.applicant, pledges)
+ loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
+ create_pledge(loan_application)
- loan = create_demand_loan(self.applicant, "Demand Loan", loan_security_pledge.name,
+ loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
posting_date=get_first_day(nowdate()))
loan.submit()
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index c28994e..9605045 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -116,7 +116,7 @@
def allocate_amounts(self, paid_entries):
self.set('repayment_details', [])
self.principal_amount_paid = 0
- interest_paid = 0
+ interest_paid = self.amount_paid - self.penalty_amount
if self.amount_paid - self.penalty_amount > 0 and paid_entries:
interest_paid = self.amount_paid - self.penalty_amount
diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
index 1553844..4572e99 100644
--- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
+++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "LS-.{applicant}.-.#####",
"creation": "2019-08-29 18:48:51.371674",
"doctype": "DocType",
@@ -6,10 +7,10 @@
"engine": "InnoDB",
"field_order": [
"loan_details_section",
- "loan_application",
- "loan",
"applicant_type",
"applicant",
+ "loan",
+ "loan_application",
"column_break_3",
"company",
"pledge_time",
@@ -55,15 +56,13 @@
"fieldname": "loan",
"fieldtype": "Link",
"label": "Loan",
- "options": "Loan",
- "read_only": 1
+ "options": "Loan"
},
{
"fieldname": "loan_application",
"fieldtype": "Link",
"label": "Loan Application",
- "options": "Loan Application",
- "read_only": 1
+ "options": "Loan Application"
},
{
"fieldname": "total_security_value",
@@ -133,7 +132,8 @@
}
],
"is_submittable": 1,
- "modified": "2019-10-10 13:22:53.297519",
+ "links": [],
+ "modified": "2020-07-02 23:38:24.002382",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security Pledge",
diff --git a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json
index db260a4..a55b482 100644
--- a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json
+++ b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "LM-LSP-.####",
"creation": "2019-09-03 18:20:31.382887",
"doctype": "DocType",
@@ -46,6 +47,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Loan Security Price",
+ "options": "Company:company:default_currency",
"reqd": 1
},
{
@@ -79,7 +81,8 @@
"read_only": 1
}
],
- "modified": "2019-10-26 09:46:46.069667",
+ "links": [],
+ "modified": "2020-06-11 03:41:33.900340",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security Price",
diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index 308c438..ffd9673 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -19,7 +19,9 @@
return
if security_value >= loan_security_shortfall.shortfall_amount:
- frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, "status", "Completed")
+ frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, {
+ "status": "Completed",
+ "shortfall_value": loan_security_shortfall.shortfall_amount})
else:
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name,
"shortfall_amount", loan_security_shortfall.shortfall_amount - security_value)
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index b58f999..add7bbf 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -44,7 +44,7 @@
for d in self.get('items'):
if d.serial_no:
serial_nos = get_valid_serial_nos(d.serial_no)
- self.validate_serial_no(serial_nos, d.start_date)
+ self.validate_serial_no(d.item_code, serial_nos, d.start_date)
self.update_amc_date(serial_nos, d.end_date)
no_email_sp = []
@@ -178,14 +178,18 @@
serial_no_doc.amc_expiry_date = amc_expiry_date
serial_no_doc.save()
- def validate_serial_no(self, serial_nos, amc_start_date):
+ def validate_serial_no(self, item_code, serial_nos, amc_start_date):
for serial_no in serial_nos:
sr_details = frappe.db.get_value("Serial No", serial_no,
- ["warranty_expiry_date", "amc_expiry_date", "warehouse", "delivery_date"], as_dict=1)
+ ["warranty_expiry_date", "amc_expiry_date", "warehouse", "delivery_date", "item_code"], as_dict=1)
if not sr_details:
frappe.throw(_("Serial No {0} not found").format(serial_no))
+ if sr_details.get("item_code") != item_code:
+ frappe.throw(_("Serial No {0} does not belong to Item {1}")
+ .format(frappe.bold(serial_no), frappe.bold(item_code)), title="Invalid")
+
if sr_details.warranty_expiry_date \
and getdate(sr_details.warranty_expiry_date) >= getdate(amc_start_date):
throw(_("Serial No {0} is under warranty upto {1}")
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
index c797b7e..1192568 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
@@ -701,7 +701,7 @@
"columns": 0,
"default": "Draft",
"fieldname": "status",
- "fieldtype": "Data",
+ "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -1001,7 +1001,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-21 14:44:44.911402",
+ "modified": "2020-07-15 14:44:44.911402",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit",
diff --git a/erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json b/erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json
new file mode 100644
index 0000000..d74ae2f
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_chart/completed_operation/completed_operation.json
@@ -0,0 +1,28 @@
+{
+ "based_on": "creation",
+ "chart_name": "Completed Operation",
+ "chart_type": "Sum",
+ "creation": "2020-07-08 22:40:22.441658",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Work Order Operation",
+ "filters_json": "[[\"Work Order Operation\",\"docstatus\",\"=\",1,false]]",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-21 16:57:09.767009",
+ "modified": "2020-07-21 16:57:55.719802",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Completed Operation",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Quarterly",
+ "timeseries": 1,
+ "timespan": "Last Year",
+ "type": "Line",
+ "use_report_chart": 0,
+ "value_based_on": "completed_qty",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/dashboard_chart/job_card_analysis/job_card_analysis.json b/erpnext/manufacturing/dashboard_chart/job_card_analysis/job_card_analysis.json
new file mode 100644
index 0000000..e3cbba6
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_chart/job_card_analysis/job_card_analysis.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "Job Card Analysis",
+ "chart_type": "Report",
+ "creation": "2020-07-08 22:40:22.549096",
+ "custom_options": "{\"barOptions\": {\"stacked\": 1}}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.defaults.get_user_default(\\\"year_start_date\\\")\",\"to_date\":\"frappe.defaults.get_user_default(\\\"year_end_date\\\")\"}",
+ "filters_json": "{\"docstatus\":1,\"range\":\"Monthly\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-21 17:47:06.537924",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card Analysis",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Job Card Summary",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/dashboard_chart/last_month_downtime_analysis/last_month_downtime_analysis.json b/erpnext/manufacturing/dashboard_chart/last_month_downtime_analysis/last_month_downtime_analysis.json
new file mode 100644
index 0000000..46d2215
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_chart/last_month_downtime_analysis/last_month_downtime_analysis.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "Last Month Downtime Analysis",
+ "chart_type": "Report",
+ "creation": "2020-07-08 22:40:22.516460",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{}",
+ "filters_json": "{\"from_date\":\"2020-06-21 00:00:00\",\"to_date\":\"2020-07-21 18:46:45\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-21 18:46:50.767333",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Last Month Downtime Analysis",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Downtime Analysis",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/dashboard_chart/pending_work_order/pending_work_order.json b/erpnext/manufacturing/dashboard_chart/pending_work_order/pending_work_order.json
new file mode 100644
index 0000000..91cd12b
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_chart/pending_work_order/pending_work_order.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "Pending Work Order",
+ "chart_type": "Report",
+ "creation": "2020-07-08 22:40:22.499217",
+ "custom_options": "{\"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"height\": 300}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.defaults.get_user_default(\\\"year_start_date\\\")\",\"to_date\":\"frappe.defaults.get_user_default(\\\"year_end_date\\\")\"}",
+ "filters_json": "{\"charts_based_on\":\"Age\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-21 17:46:42.917598",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Pending Work Order",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Work Order Summary",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Donut",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/dashboard_chart/produced_quantity/produced_quantity.json b/erpnext/manufacturing/dashboard_chart/produced_quantity/produced_quantity.json
new file mode 100644
index 0000000..ba1a29d
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_chart/produced_quantity/produced_quantity.json
@@ -0,0 +1,30 @@
+{
+ "based_on": "modified",
+ "chart_name": "Produced Quantity",
+ "chart_type": "Sum",
+ "creation": "2020-07-08 22:40:22.416285",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Work Order",
+ "dynamic_filters_json": "[[\"Work Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Work Order\",\"docstatus\",\"=\",\"1\",false]]",
+ "group_by_type": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-21 17:46:34.058882",
+ "modified": "2020-07-21 17:54:11.233531",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Produced Quantity",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 1,
+ "timespan": "Last Year",
+ "type": "Line",
+ "use_report_chart": 0,
+ "value_based_on": "produced_qty",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/dashboard_chart/quality_inspection_analysis/quality_inspection_analysis.json b/erpnext/manufacturing/dashboard_chart/quality_inspection_analysis/quality_inspection_analysis.json
new file mode 100644
index 0000000..8388f3d
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_chart/quality_inspection_analysis/quality_inspection_analysis.json
@@ -0,0 +1,25 @@
+{
+ "chart_name": "Quality Inspection Analysis",
+ "chart_type": "Report",
+ "creation": "2020-07-08 22:40:22.483617",
+ "custom_options": "{\"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"height\": 300}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "filters_json": "{\"from_date\":\"2019-07-09\",\"to_date\":\"2020-07-09\"}",
+ "idx": 0,
+ "use_report_chart": 1,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-09 12:15:51.564487",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Quality Inspection Analysis",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Quality Inspection Summary",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Donut",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/dashboard_chart/work_order_analysis/work_order_analysis.json b/erpnext/manufacturing/dashboard_chart/work_order_analysis/work_order_analysis.json
new file mode 100644
index 0000000..879826a
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_chart/work_order_analysis/work_order_analysis.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "Work Order Analysis",
+ "chart_type": "Report",
+ "creation": "2020-07-08 22:40:22.465459",
+ "custom_options": "{\"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"height\": 300}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.defaults.get_user_default(\\\"year_start_date\\\")\",\"to_date\":\"frappe.defaults.get_user_default(\\\"year_end_date\\\")\"}",
+ "filters_json": "{\"charts_based_on\":\"Status\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-21 17:50:23.806007",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Work Order Analysis",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Work Order Summary",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Donut",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/dashboard_chart/work_order_qty_analysis/work_order_qty_analysis.json b/erpnext/manufacturing/dashboard_chart/work_order_qty_analysis/work_order_qty_analysis.json
new file mode 100644
index 0000000..9357279
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_chart/work_order_qty_analysis/work_order_qty_analysis.json
@@ -0,0 +1,26 @@
+{
+ "chart_name": "Work Order Qty Analysis",
+ "chart_type": "Report",
+ "creation": "2020-07-08 22:40:22.532889",
+ "custom_options": "{\"barOptions\": {\"stacked\": 1}}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.defaults.get_user_default(\\\"year_start_date\\\")\",\"to_date\":\"frappe.defaults.get_user_default(\\\"year_end_date\\\")\"}",
+ "filters_json": "{\"charts_based_on\":\"Quantity\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-21 17:46:59.020709",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Work Order Qty Analysis",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Work Order Summary",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 7d31a1c..c51f655 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -494,7 +494,7 @@
'image' : d.image,
'stock_uom' : d.stock_uom,
'stock_qty' : flt(d.stock_qty),
- 'rate' : d.base_rate,
+ 'rate' : flt(d.base_rate) / flt(d.conversion_factor),
'include_item_in_manufacturing': d.include_item_in_manufacturing
}))
@@ -910,6 +910,8 @@
return out
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def item_query(doctype, txt, searchfield, start, page_len, filters):
meta = frappe.get_meta("Item", cached=True)
searchfields = meta.get_search_fields()
@@ -989,4 +991,4 @@
},
}, target_doc, postprocess)
- return doc
\ No newline at end of file
+ return doc
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 560286e..c889237 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -98,11 +98,17 @@
elif self.get_items_from == "Material Request":
self.get_mr_items()
+ def get_so_mr_list(self, field, table):
+ """Returns a list of Sales Orders or Material Requests from the respective tables"""
+ so_mr_list = [d.get(field) for d in self.get(table) if d.get(field)]
+ return so_mr_list
+
def get_so_items(self):
- so_list = [d.sales_order for d in self.sales_orders if d.sales_order]
- if not so_list:
- msgprint(_("Please enter Sales Orders in the above table"))
- return []
+ # Check for empty table or empty rows
+ if not self.get("sales_orders") or not self.get_so_mr_list("sales_order", "sales_orders"):
+ frappe.throw(_("Please fill the Sales Orders table"), title=_("Sales Orders Required"))
+
+ so_list = self.get_so_mr_list("sales_order", "sales_orders")
item_condition = ""
if self.item_code:
@@ -134,10 +140,11 @@
self.calculate_total_planned_qty()
def get_mr_items(self):
- mr_list = [d.material_request for d in self.material_requests if d.material_request]
- if not mr_list:
- msgprint(_("Please enter Material Requests in the above table"))
- return []
+ # Check for empty table or empty rows
+ if not self.get("material_requests") or not self.get_so_mr_list("material_request", "material_requests"):
+ frappe.throw(_("Please fill the Material Requests table"), title=_("Material Requests Required"))
+
+ mr_list = self.get_so_mr_list("material_request", "material_requests")
item_condition = ""
if self.item_code:
@@ -628,16 +635,19 @@
if warehouse_list:
warehouses = list(set(warehouse_list))
-
+
if doc.get("for_warehouse") and doc.get("for_warehouse") in warehouses:
warehouses.remove(doc.get("for_warehouse"))
warehouse_list = None
doc['mr_items'] = []
+
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
- if not po_items:
- frappe.throw(_("Items are required to pull the raw materials which is associated with it."))
+ # Check for empty table or empty rows
+ if not po_items or not [row.get('item_code') for row in po_items if row.get('item_code')]:
+ frappe.throw(_("Items to Manufacture are required to pull the Raw Materials associated with it."),
+ title=_("Items Required"))
company = doc.get('company')
ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty')
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index e2233a3..b7d968e 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -631,6 +631,8 @@
bom.set_bom_material_details()
return bom
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_bom_operations(doctype, txt, searchfield, start, page_len, filters):
if txt:
filters['operation'] = ('like', '%%%s%%' % txt)
diff --git a/erpnext/manufacturing/manufacturing_dashboard/manufacturing/manufacturing.json b/erpnext/manufacturing/manufacturing_dashboard/manufacturing/manufacturing.json
new file mode 100644
index 0000000..314efe7
--- /dev/null
+++ b/erpnext/manufacturing/manufacturing_dashboard/manufacturing/manufacturing.json
@@ -0,0 +1,62 @@
+{
+ "cards": [
+ {
+ "card": "Monthly Total Work Order"
+ },
+ {
+ "card": "Monthly Completed Work Order"
+ },
+ {
+ "card": "Ongoing Job Card"
+ },
+ {
+ "card": "Monthly Quality Inspection"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "Produced Quantity",
+ "width": "Half"
+ },
+ {
+ "chart": "Completed Operation",
+ "width": "Half"
+ },
+ {
+ "chart": "Work Order Analysis",
+ "width": "Half"
+ },
+ {
+ "chart": "Quality Inspection Analysis",
+ "width": "Half"
+ },
+ {
+ "chart": "Pending Work Order",
+ "width": "Half"
+ },
+ {
+ "chart": "Last Month Downtime Analysis",
+ "width": "Half"
+ },
+ {
+ "chart": "Work Order Qty Analysis",
+ "width": "Full"
+ },
+ {
+ "chart": "Job Card Analysis",
+ "width": "Full"
+ }
+ ],
+ "creation": "2020-07-08 22:40:22.626607",
+ "dashboard_name": "Manufacturing",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 0,
+ "is_standard": 1,
+ "modified": "2020-07-09 12:39:39.455039",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Manufacturing",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
index a36b63a..7317152 100644
--- a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
@@ -50,7 +50,7 @@
"step": "Explore Manufacturing Settings"
}
],
- "subtitle": "Products, Raw Materials, BOM, Work Order and more.",
- "success_message": "Manufacturing module is all setup!",
- "title": "Let's Set Up the Manufacturing Module"
+ "subtitle": "Products, Raw Materials, BOM, Work Order, and more.",
+ "success_message": "Manufacturing module is all set up!",
+ "title": "Let's Set Up the Manufacturing Module."
}
diff --git a/erpnext/manufacturing/number_card/monthly_completed_work_order/monthly_completed_work_order.json b/erpnext/manufacturing/number_card/monthly_completed_work_order/monthly_completed_work_order.json
new file mode 100644
index 0000000..36c0b9a
--- /dev/null
+++ b/erpnext/manufacturing/number_card/monthly_completed_work_order/monthly_completed_work_order.json
@@ -0,0 +1,19 @@
+{
+ "creation": "2020-07-08 22:40:22.575086",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "filters_json": "[[\"Work Order\",\"status\",\"=\",\"Completed\"],[\"Work Order\",\"docstatus\",\"=\",1],[\"Work Order\",\"creation\",\"between\",[\"2020-06-08\",\"2020-07-08\"]]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Monthly Completed Work Orders",
+ "modified": "2020-07-09 12:22:54.809813",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Monthly Completed Work Order",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/number_card/monthly_quality_inspection/monthly_quality_inspection.json b/erpnext/manufacturing/number_card/monthly_quality_inspection/monthly_quality_inspection.json
new file mode 100644
index 0000000..91a4536
--- /dev/null
+++ b/erpnext/manufacturing/number_card/monthly_quality_inspection/monthly_quality_inspection.json
@@ -0,0 +1,19 @@
+{
+ "creation": "2020-07-08 22:40:22.606867",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Quality Inspection",
+ "filters_json": "[[\"Quality Inspection\",\"docstatus\",\"=\",1],[\"Quality Inspection\",\"creation\",\"between\",[\"2020-06-08\",\"2020-07-08\"]]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Monthly Quality Inspections",
+ "modified": "2020-07-09 12:23:34.838154",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Monthly Quality Inspection",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/number_card/monthly_total_work_order/monthly_total_work_order.json b/erpnext/manufacturing/number_card/monthly_total_work_order/monthly_total_work_order.json
new file mode 100644
index 0000000..80d3b15
--- /dev/null
+++ b/erpnext/manufacturing/number_card/monthly_total_work_order/monthly_total_work_order.json
@@ -0,0 +1,19 @@
+{
+ "creation": "2020-07-08 22:40:22.562715",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "filters_json": "[[\"Work Order\",\"docstatus\",\"=\",1],[\"Work Order\",\"creation\",\"between\",[\"2020-06-08\",\"2020-07-08\"]]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Monthly Total Work Orders",
+ "modified": "2020-07-09 12:22:25.698795",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Monthly Total Work Order",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/number_card/ongoing_job_card/ongoing_job_card.json b/erpnext/manufacturing/number_card/ongoing_job_card/ongoing_job_card.json
new file mode 100644
index 0000000..ba23ff3
--- /dev/null
+++ b/erpnext/manufacturing/number_card/ongoing_job_card/ongoing_job_card.json
@@ -0,0 +1,19 @@
+{
+ "creation": "2020-07-08 22:40:22.592042",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Job Card",
+ "filters_json": "[[\"Job Card\",\"status\",\"!=\",\"Completed\"],[\"Job Card\",\"docstatus\",\"=\",1]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Ongoing Job Cards",
+ "modified": "2020-07-09 12:23:18.218233",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Ongoing Job Card",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
index c5627e0..dc424b7 100644
--- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
+++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py
@@ -19,7 +19,7 @@
"options": "Work Order",
"width": 120
}]
-
+
if not filters.get('bom_no'):
columns.extend([
{
@@ -30,7 +30,7 @@
"width": 180
}
])
-
+
columns.extend([
{
"label": _("Finished Good"),
@@ -73,7 +73,7 @@
])
return columns
-
+
def get_data(filters):
cond = "1=1"
@@ -95,6 +95,7 @@
return results
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_work_orders(doctype, txt, searchfield, start, page_len, filters):
cond = "1=1"
if filters.get('bom_no'):
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
index 5ac3923..ebc01c6 100644
--- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -369,6 +369,3 @@
"fieldtype": "Float",
"width": 140
}])
-
-def document_query(doctype, txt, searchfield, start, page_len, filters):
- pass
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 17fbcc2..a24f5f7 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -14,6 +14,7 @@
erpnext.patches.v4_0.move_warehouse_user_to_restrictions
erpnext.patches.v4_0.global_defaults_to_system_settings
erpnext.patches.v4_0.update_incharge_name_to_sales_person_in_maintenance_schedule
+execute:frappe.reload_doc("accounts", "doctype", "POS Payment Method") #2020-05-28
execute:frappe.reload_doc("HR", "doctype", "HR Settings") #2020-01-16
execute:frappe.reload_doc('stock', 'doctype', 'warehouse') # 2017-04-24
execute:frappe.reload_doc('accounts', 'doctype', 'sales_invoice') # 2016-08-31
@@ -437,7 +438,6 @@
erpnext.patches.v8_7.sync_india_custom_fields
erpnext.patches.v8_7.fix_purchase_receipt_status
erpnext.patches.v8_6.rename_bom_update_tool
-erpnext.patches.v8_7.set_offline_in_pos_settings #11-09-17
erpnext.patches.v8_9.add_setup_progress_actions #08-09-2017 #26-09-2017 #22-11-2017 #15-12-2017
erpnext.patches.v8_9.rename_company_sales_target_field
erpnext.patches.v8_8.set_bom_rate_as_per_uom
@@ -677,6 +677,8 @@
erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
erpnext.patches.v12_0.fix_quotation_expired_status
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
+erpnext.patches.v12_0.rename_pos_closing_doctype
+erpnext.patches.v13_0.replace_pos_payment_mode_table
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype
erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
@@ -695,8 +697,11 @@
execute:frappe.delete_doc("Report", "Department Analytics")
execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True)
erpnext.patches.v12_0.update_uom_conversion_factor
+execute:frappe.delete_doc_if_exists("Page", "pos") #29-05-2020
erpnext.patches.v13_0.delete_old_purchase_reports
erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
+erpnext.patches.v13_0.update_subscription
+erpnext.patches.v12_0.unhide_cost_center_field
erpnext.patches.v13_0.update_sla_enhancements
erpnext.patches.v12_0.update_address_template_for_india
erpnext.patches.v13_0.update_deferred_settings
@@ -706,3 +711,10 @@
erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020
erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020
erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020
+erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020
+erpnext.patches.v12_0.add_taxjar_integration_field
+erpnext.patches.v12_0.fix_percent_complete_for_projects
+erpnext.patches.v13_0.delete_report_requested_items_to_order
+erpnext.patches.v12_0.update_item_tax_template_company
+erpnext.patches.v13_0.move_branch_code_to_bank_account
+erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes
diff --git a/erpnext/patches/v11_0/refactor_autoname_naming.py b/erpnext/patches/v11_0/refactor_autoname_naming.py
index d67c723..5dc5d3b 100644
--- a/erpnext/patches/v11_0/refactor_autoname_naming.py
+++ b/erpnext/patches/v11_0/refactor_autoname_naming.py
@@ -54,7 +54,7 @@
'Payroll Entry': 'HR-PRUN-.YYYY.-.#####',
'Period Closing Voucher': 'ACC-PCV-.YYYY.-.#####',
'Plant Analysis': 'AG-PLA-.YYYY.-.#####',
- 'POS Closing Voucher': 'POS-CLO-.YYYY.-.#####',
+ 'POS Closing Entry': 'POS-CLO-.YYYY.-.#####',
'Prepared Report': 'SYS-PREP-.YYYY.-.#####',
'Program Enrollment': 'EDU-ENR-.YYYY.-.#####',
'Quotation Item': '',
diff --git a/erpnext/patches/v12_0/add_taxjar_integration_field.py b/erpnext/patches/v12_0/add_taxjar_integration_field.py
new file mode 100644
index 0000000..4c823e1
--- /dev/null
+++ b/erpnext/patches/v12_0/add_taxjar_integration_field.py
@@ -0,0 +1,12 @@
+from __future__ import unicode_literals
+
+import frappe
+from erpnext.regional.united_states.setup import make_custom_fields
+
+
+def execute():
+ company = frappe.get_all('Company', filters={'country': 'United States'})
+ if not company:
+ return
+
+ make_custom_fields()
diff --git a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py
index 82c8f5c..43bd0cc 100644
--- a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py
+++ b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py
@@ -5,6 +5,8 @@
def execute():
frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True)
+ frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True)
+ frappe.reload_doc('crm', 'doctype', 'lost_reason_detail', force=True)
company = frappe.get_all('Company', filters = {'country': 'United States'})
if not company:
diff --git a/erpnext/patches/v12_0/fix_percent_complete_for_projects.py b/erpnext/patches/v12_0/fix_percent_complete_for_projects.py
new file mode 100644
index 0000000..3622df6
--- /dev/null
+++ b/erpnext/patches/v12_0/fix_percent_complete_for_projects.py
@@ -0,0 +1,14 @@
+import frappe
+from frappe.utils import flt
+
+def execute():
+ for project in frappe.get_all("Project", fields=["name", "percent_complete_method"]):
+ total = frappe.db.count('Task', dict(project=project.name))
+ if project.percent_complete_method == "Task Completion" and total > 0:
+ completed = frappe.db.sql("""select count(name) from tabTask where
+ project=%s and status in ('Cancelled', 'Completed')""", project.name)[0][0]
+ percent_complete = flt(flt(completed) / total * 100, 2)
+ if project.percent_complete != percent_complete:
+ frappe.db.set_value("Project", project.name, "percent_complete", percent_complete)
+ if percent_complete == 100:
+ frappe.db.set_value("Project", project.name, "status", "Completed")
diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
index 1ddbae6..a670ade 100644
--- a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
+++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
@@ -7,8 +7,7 @@
if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account') and frappe.db.has_column('Bank Account', 'swift_number'):
frappe.db.sql("""
UPDATE `tabBank` b, `tabBank Account` ba
- SET b.swift_number = ba.swift_number, b.branch_code = ba.branch_code
- WHERE b.name = ba.bank
+ SET b.swift_number = ba.swift_number WHERE b.name = ba.bank
""")
frappe.reload_doc('accounts', 'doctype', 'bank_account')
diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
index 8889056..06331d7 100644
--- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
+++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
@@ -100,8 +100,10 @@
tax_type = None
else:
company = get_company(parts[-1], parenttype, parent)
- parent_account = frappe.db.get_value("Account",
- filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
+ parent_account = frappe.get_value("Account", {"account_name": account_name, "company": company}, "parent_account")
+ if not parent_account:
+ parent_account = frappe.db.get_value("Account",
+ filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
if not parent_account:
parent_account = frappe.db.get_value("Account",
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 1, "company": company})
@@ -115,8 +117,11 @@
if not tax_type:
account = frappe.new_doc("Account")
account.update(filters)
- account.insert()
- tax_type = account.name
+ try:
+ account.insert()
+ tax_type = account.name
+ except frappe.DuplicateEntryError:
+ tax_type = frappe.db.get_value("Account", {"account_name": account_name, "company": company}, "name")
account_type = frappe.get_cached_value("Account", tax_type, "account_type")
diff --git a/erpnext/patches/v12_0/rename_pos_closing_doctype.py b/erpnext/patches/v12_0/rename_pos_closing_doctype.py
new file mode 100644
index 0000000..0577f81
--- /dev/null
+++ b/erpnext/patches/v12_0/rename_pos_closing_doctype.py
@@ -0,0 +1,25 @@
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ if frappe.db.table_exists("POS Closing Voucher"):
+ if not frappe.db.exists("DocType", "POS Closing Entry"):
+ frappe.rename_doc('DocType', 'POS Closing Voucher', 'POS Closing Entry', force=True)
+
+ if not frappe.db.exists('DocType', 'POS Closing Entry Taxes'):
+ frappe.rename_doc('DocType', 'POS Closing Voucher Taxes', 'POS Closing Entry Taxes', force=True)
+
+ if not frappe.db.exists('DocType', 'POS Closing Voucher Details'):
+ frappe.rename_doc('DocType', 'POS Closing Voucher Details', 'POS Closing Entry Detail', force=True)
+
+ frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry')
+ frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry Taxes')
+ frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry Detail')
+
+ if frappe.db.exists("DocType", "POS Closing Voucher"):
+ frappe.delete_doc("DocType", "POS Closing Voucher")
+ frappe.delete_doc("DocType", "POS Closing Voucher Taxes")
+ frappe.delete_doc("DocType", "POS Closing Voucher Details")
+ frappe.delete_doc("DocType", "POS Closing Voucher Invoices")
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/unhide_cost_center_field.py b/erpnext/patches/v12_0/unhide_cost_center_field.py
new file mode 100644
index 0000000..6005ab7
--- /dev/null
+++ b/erpnext/patches/v12_0/unhide_cost_center_field.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2017, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.db.sql("""
+ DELETE FROM `tabProperty Setter`
+ WHERE doc_type in ('Sales Invoice', 'Purchase Invoice', 'Payment Entry')
+ AND field_name = 'cost_center'
+ AND property = 'hidden'
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/update_item_tax_template_company.py b/erpnext/patches/v12_0/update_item_tax_template_company.py
new file mode 100644
index 0000000..f749699
--- /dev/null
+++ b/erpnext/patches/v12_0/update_item_tax_template_company.py
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc('accounts', 'doctype', 'item_tax_template')
+
+ item_tax_template_list = frappe.get_list('Item Tax Template')
+ for template in item_tax_template_list:
+ doc = frappe.get_doc('Item Tax Template', template.name)
+ for tax in doc.taxes:
+ doc.company = frappe.get_value('Account', tax.tax_type, 'company')
+ break
+ doc.save()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/delete_report_requested_items_to_order.py b/erpnext/patches/v13_0/delete_report_requested_items_to_order.py
new file mode 100644
index 0000000..94a9fa8
--- /dev/null
+++ b/erpnext/patches/v13_0/delete_report_requested_items_to_order.py
@@ -0,0 +1,12 @@
+import frappe
+
+def execute():
+ """ Check for one or multiple Auto Email Reports and delete """
+ auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": "Requested Items to Order"}, ["name"])
+ for auto_email_report in auto_email_reports:
+ frappe.delete_doc("Auto Email Report", auto_email_report[0])
+
+ frappe.db.sql("""
+ DELETE FROM `tabReport`
+ WHERE name = 'Requested Items to Order'
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
new file mode 100644
index 0000000..5920bf1
--- /dev/null
+++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
@@ -0,0 +1,51 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+ if frappe.db.exists('DocType', 'Lab Test') and frappe.db.exists('DocType', 'Lab Test Template'):
+ # rename child doctypes
+ doctypes = {
+ 'Lab Test Groups': 'Lab Test Group Template',
+ 'Normal Test Items': 'Normal Test Result',
+ 'Sensitivity Test Items': 'Sensitivity Test Result',
+ 'Special Test Items': 'Descriptive Test Result',
+ 'Special Test Template': 'Descriptive Test Template'
+ }
+
+ frappe.reload_doc('healthcare', 'doctype', 'lab_test')
+ frappe.reload_doc('healthcare', 'doctype', 'lab_test_template')
+
+ for old_dt, new_dt in doctypes.items():
+ if not frappe.db.table_exists(new_dt) and frappe.db.table_exists(old_dt):
+ frappe.rename_doc('DocType', old_dt, new_dt, force=True)
+ frappe.reload_doc('healthcare', 'doctype', frappe.scrub(new_dt))
+ frappe.delete_doc_if_exists('DocType', old_dt)
+
+ parent_fields = {
+ 'Lab Test Group Template': 'lab_test_groups',
+ 'Descriptive Test Template': 'descriptive_test_templates',
+ 'Normal Test Result': 'normal_test_items',
+ 'Sensitivity Test Result': 'sensitivity_test_items',
+ 'Descriptive Test Result': 'descriptive_test_items'
+ }
+
+ for doctype, parentfield in parent_fields.items():
+ frappe.db.sql("""
+ UPDATE `tab{0}`
+ SET parentfield = %(parentfield)s
+ """.format(doctype), {'parentfield': parentfield})
+
+ # rename field
+ frappe.reload_doc('healthcare', 'doctype', 'lab_test')
+ if frappe.db.has_column('Lab Test', 'special_toggle'):
+ rename_field('Lab Test', 'special_toggle', 'descriptive_toggle')
+
+ if frappe.db.exists('DocType', 'Lab Test Group Template'):
+ # fix select field option
+ frappe.reload_doc('healthcare', 'doctype', 'lab_test_group_template')
+ frappe.db.sql("""
+ UPDATE `tabLab Test Group Template`
+ SET template_or_new_line = 'Add New Line'
+ WHERE template_or_new_line = 'Add new line'
+ """)
diff --git a/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py b/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py
new file mode 100644
index 0000000..ee77340
--- /dev/null
+++ b/erpnext/patches/v13_0/loyalty_points_entry_for_pos_invoice.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+ '''`sales_invoice` field from loyalty point entry is splitted into `invoice_type` & `invoice` fields'''
+
+ frappe.reload_doc("Accounts", "doctype", "loyalty_point_entry")
+
+ if not frappe.db.has_column('Loyalty Point Entry', 'sales_invoice'):
+ return
+
+ frappe.db.sql(
+ """UPDATE `tabLoyalty Point Entry` lpe
+ SET lpe.`invoice_type` = 'Sales Invoice', lpe.`invoice` = lpe.`sales_invoice`
+ WHERE lpe.`sales_invoice` IS NOT NULL
+ AND (lpe.`invoice` IS NULL OR lpe.`invoice` = '')""")
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/move_branch_code_to_bank_account.py b/erpnext/patches/v13_0/move_branch_code_to_bank_account.py
new file mode 100644
index 0000000..833ae2a
--- /dev/null
+++ b/erpnext/patches/v13_0/move_branch_code_to_bank_account.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+
+ frappe.reload_doc('accounts', 'doctype', 'bank_account')
+ frappe.reload_doc('accounts', 'doctype', 'bank')
+
+ if frappe.db.has_column('Bank', 'branch_code') and frappe.db.has_column('Bank Account', 'branch_code'):
+ frappe.db.sql("""UPDATE `tabBank` b, `tabBank Account` ba
+ SET ba.branch_code = b.branch_code
+ WHERE ba.bank = b.name AND
+ ifnull(b.branch_code, '') != '' AND ifnull(ba.branch_code, '') = ''""")
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py
new file mode 100644
index 0000000..1ca211b
--- /dev/null
+++ b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+ frappe.reload_doc("accounts", "doctype", "POS Payment Method")
+ pos_profiles = frappe.get_all("POS Profile")
+
+ for pos_profile in pos_profiles:
+ if not pos_profile.get("payments"): return
+
+ payments = frappe.db.sql("""
+ select idx, parentfield, parenttype, parent, mode_of_payment, `default` from `tabSales Invoice Payment` where parent=%s
+ """, pos_profile.name, as_dict=1)
+ if payments:
+ for payment_mode in payments:
+ pos_payment_method = frappe.new_doc("POS Payment Method")
+ pos_payment_method.idx = payment_mode.idx
+ pos_payment_method.default = payment_mode.default
+ pos_payment_method.mode_of_payment = payment_mode.mode_of_payment
+ pos_payment_method.parent = payment_mode.parent
+ pos_payment_method.parentfield = payment_mode.parentfield
+ pos_payment_method.parenttype = payment_mode.parenttype
+ pos_payment_method.db_insert()
+
+ frappe.db.sql("""delete from `tabSales Invoice Payment` where parent=%s""", pos_profile.name)
diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
index 331c559..adfa20e 100644
--- a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
+++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
@@ -6,7 +6,6 @@
import frappe
from frappe.utils import add_to_date
-from frappe.utils.dashboard import get_config, make_records
def execute():
frappe.reload_doc("manufacturing", "doctype", "work_order")
diff --git a/erpnext/patches/v13_0/update_subscription.py b/erpnext/patches/v13_0/update_subscription.py
new file mode 100644
index 0000000..871ebf1
--- /dev/null
+++ b/erpnext/patches/v13_0/update_subscription.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from six import iteritems
+
+def execute():
+
+ frappe.reload_doc('accounts', 'doctype', 'subscription')
+ frappe.reload_doc('accounts', 'doctype', 'subscription_invoice')
+ frappe.reload_doc('accounts', 'doctype', 'subscription_plan')
+
+ if frappe.db.has_column('Subscription', 'customer'):
+ frappe.db.sql("""
+ UPDATE `tabSubscription`
+ SET
+ start_date = start,
+ party_type = 'Customer',
+ party = customer,
+ sales_tax_template = tax_template
+ WHERE IFNULL(party,'') = ''
+ """)
+
+ frappe.db.sql("""
+ UPDATE `tabSubscription Invoice`
+ SET document_type = 'Sales Invoice'
+ WHERE IFNULL(document_type, '') = ''
+ """)
+
+ price_determination_map = {
+ 'Fixed rate': 'Fixed Rate',
+ 'Based on price list': 'Based On Price List'
+ }
+
+ for key, value in iteritems(price_determination_map):
+ frappe.db.sql("""
+ UPDATE `tabSubscription Plan`
+ SET price_determination = %s
+ WHERE price_determination = %s
+ """, (value, key))
\ No newline at end of file
diff --git a/erpnext/patches/v8_7/set_offline_in_pos_settings.py b/erpnext/patches/v8_7/set_offline_in_pos_settings.py
deleted file mode 100644
index 7d2882e..0000000
--- a/erpnext/patches/v8_7/set_offline_in_pos_settings.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright (c) 2017, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-
-def execute():
- frappe.reload_doc('accounts', 'doctype', 'pos_field')
- frappe.reload_doc('accounts', 'doctype', 'pos_settings')
-
- doc = frappe.get_doc('POS Settings')
- doc.use_pos_in_offline_mode = 1
- doc.save()
\ No newline at end of file
diff --git "a/erpnext/payroll/dashboard_chart/department_wise_salary\050last_month\051/department_wise_salary\050last_month\051.json" "b/erpnext/payroll/dashboard_chart/department_wise_salary\050last_month\051/department_wise_salary\050last_month\051.json"
new file mode 100644
index 0000000..61ae86f
--- /dev/null
+++ "b/erpnext/payroll/dashboard_chart/department_wise_salary\050last_month\051/department_wise_salary\050last_month\051.json"
@@ -0,0 +1,30 @@
+{
+ "aggregate_function_based_on": "rounded_total",
+ "chart_name": "Department Wise Salary(Last Month)",
+ "chart_type": "Group By",
+ "creation": "2020-07-22 11:56:34.511940",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Salary Slip",
+ "dynamic_filters_json": "[[\"Salary Slip\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Salary Slip\",\"docstatus\",\"=\",\"1\",false],[\"Salary Slip\",\"start_date\",\"Timespan\",\"last month\",false]]",
+ "group_by_based_on": "department",
+ "group_by_type": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 12:46:05.272076",
+ "modified": "2020-07-22 12:48:12.080992",
+ "modified_by": "Administrator",
+ "module": "Payroll",
+ "name": "Department Wise Salary(Last Month)",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git "a/erpnext/payroll/dashboard_chart/designation_wise_salary\050last_month\051/designation_wise_salary\050last_month\051.json" "b/erpnext/payroll/dashboard_chart/designation_wise_salary\050last_month\051/designation_wise_salary\050last_month\051.json"
new file mode 100644
index 0000000..b3c4e59
--- /dev/null
+++ "b/erpnext/payroll/dashboard_chart/designation_wise_salary\050last_month\051/designation_wise_salary\050last_month\051.json"
@@ -0,0 +1,30 @@
+{
+ "aggregate_function_based_on": "rounded_total",
+ "chart_name": "Designation Wise Salary(Last Month)",
+ "chart_type": "Group By",
+ "creation": "2020-07-22 11:56:34.550339",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Salary Slip",
+ "dynamic_filters_json": "[[\"Salary Slip\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Salary Slip\",\"docstatus\",\"=\",\"1\",false],[\"Salary Slip\",\"start_date\",\"Timespan\",\"last month\",false]]",
+ "group_by_based_on": "designation",
+ "group_by_type": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 12:22:18.412822",
+ "modified": "2020-07-22 12:39:07.923382",
+ "modified_by": "Administrator",
+ "module": "Payroll",
+ "name": "Designation Wise Salary(Last Month)",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 0,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/payroll/dashboard_chart/outgoing_salary/outgoing_salary.json b/erpnext/payroll/dashboard_chart/outgoing_salary/outgoing_salary.json
new file mode 100644
index 0000000..c77c8a5
--- /dev/null
+++ b/erpnext/payroll/dashboard_chart/outgoing_salary/outgoing_salary.json
@@ -0,0 +1,29 @@
+{
+ "based_on": "end_date",
+ "chart_name": "Outgoing Salary",
+ "chart_type": "Sum",
+ "creation": "2020-07-22 11:56:34.478848",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Salary Slip",
+ "dynamic_filters_json": "[[\"Salary Slip\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Salary Slip\",\"docstatus\",\"=\",\"1\",false]]",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "last_synced_on": "2020-07-22 12:11:27.481231",
+ "modified": "2020-07-22 12:20:05.777715",
+ "modified_by": "Administrator",
+ "module": "Payroll",
+ "name": "Outgoing Salary",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 1,
+ "timespan": "Last Year",
+ "type": "Line",
+ "use_report_chart": 0,
+ "value_based_on": "rounded_total",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/payroll/dashboard_fixtures.py b/erpnext/payroll/dashboard_fixtures.py
deleted file mode 100644
index ae7a9ff..0000000
--- a/erpnext/payroll/dashboard_fixtures.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-import erpnext
-from erpnext.hr.dashboard_fixtures import get_dashboards_chart_doc, get_number_cards_doc
-import json
-from frappe import _
-
-def get_data():
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards(),
- })
-
-def get_dashboards():
- dashboards = []
- dashboards.append(get_payroll_dashboard())
- return dashboards
-
-def get_payroll_dashboard():
- return {
- "name": "Payroll",
- "dashboard_name": "Payroll",
- "is_default": 1,
- "charts": [
- { "chart": "Outgoing Salary", "width": "Full"},
- { "chart": "Designation Wise Salary(Last Month)", "width": "Half"},
- { "chart": "Department Wise Salary(Last Month)", "width": "Half"},
- ],
- "cards": [
- {"card": "Total Declaration Submitted"},
- {"card": "Total Salary Structure"},
- {"card": "Total Incentive Given(Last month)"},
- {"card": "Total Outgoing Salary(Last month)"},
- ]
- }
-
-def get_charts():
- dashboard_charts= [
- get_dashboards_chart_doc('Outgoing Salary', "Sum", "Line",
- document_type = "Salary Slip", based_on="end_date",
- value_based_on = "rounded_total", time_interval = "Monthly", timeseries = 1,
- filters_json = json.dumps([["Salary Slip", "docstatus", "=", 1]]))
- ]
-
- dashboard_charts.append(
- get_dashboards_chart_doc('Department Wise Salary(Last Month)', "Group By", "Bar",
- document_type = "Salary Slip", group_by_type="Sum", group_by_based_on="department",
- time_interval = "Monthly", aggregate_function_based_on = "rounded_total",
- filters_json = json.dumps([
- ["Salary Slip", "docstatus", "=", 1],
- ["Salary Slip", "start_date", "Previous","1 month"]
- ])
- )
- )
-
- dashboard_charts.append(
- get_dashboards_chart_doc('Designation Wise Salary(Last Month)', "Group By", "Bar",
- document_type = "Salary Slip", group_by_type="Sum", group_by_based_on="designation",
- time_interval = "Monthly", aggregate_function_based_on = "rounded_total",
- filters_json = json.dumps([
- ["Salary Slip", "docstatus", "=", 1],
- ["Salary Slip", "start_date", "Previous","1 month"]
- ])
- )
- )
-
- return dashboard_charts
-
-def get_number_cards():
- number_cards = [get_number_cards_doc("Employee Tax Exemption Declaration", "Total Declaration Submitted", filters_json = json.dumps([
- ["Employee Tax Exemption Declaration", "docstatus", "=","1"],
- ["Employee Tax Exemption Declaration","creation","Previous","1 year"]
- ])
- )]
-
- number_cards.append(get_number_cards_doc("Employee Incentive", "Total Incentive Given(Last month)",
- time_interval = "Monthly", func = "Sum", aggregate_function_based_on = "incentive_amount",
- filters_json = json.dumps([
- ["Employee Incentive", "docstatus", "=", 1],
- ["Employee Incentive","payroll_date","Previous","1 year"]
- ]))
- )
-
- number_cards.append(get_number_cards_doc("Salary Slip", "Total Outgoing Salary(Last month)",
- time_interval = "Monthly", time_span= "Monthly", func = "Sum", aggregate_function_based_on = "rounded_total",
- filters_json = json.dumps([
- ["Salary Slip", "docstatus", "=", 1],
- ["Salary Slip", "start_date","Previous","1 month"]
- ]))
- )
- number_cards.append(get_number_cards_doc("Salary Structure", "Total Salary Structure",
- filters_json = json.dumps([
- ["Salary Structure", "docstatus", "=", 1]
- ]))
- )
-
- return number_cards
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.js b/erpnext/payroll/doctype/additional_salary/additional_salary.js
index fb42b6f..d56cd4e 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.js
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.js
@@ -8,8 +8,7 @@
frm.set_query("employee", function() {
return {
filters: {
- company: frm.doc.company,
- status: "Active"
+ company: frm.doc.company
}
};
});
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index e369ba7..ef174bd 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -33,12 +33,16 @@
frappe.throw(_("From Date can not be greater than To Date."))
if date_of_joining:
- if getdate(self.payroll_date) < getdate(date_of_joining):
+ if self.payroll_date and getdate(self.payroll_date) < getdate(date_of_joining):
frappe.throw(_("Payroll date can not be less than employee's joining date."))
- elif getdate(self.from_date) < getdate(date_of_joining):
+ elif self.from_date and getdate(self.from_date) < getdate(date_of_joining):
frappe.throw(_("From date can not be less than employee's joining date."))
- elif relieving_date and getdate(self.to_date) > getdate(relieving_date):
+
+ if relieving_date:
+ if self.to_date and getdate(self.to_date) > getdate(relieving_date):
frappe.throw(_("To date can not be greater than employee's relieving date."))
+ if self.payroll_date and getdate(self.payroll_date) > getdate(relieving_date):
+ frappe.throw(_("Payroll date can not be greater than employee's relieving date."))
def get_amount(self, sal_start_date, sal_end_date):
start_date = getdate(sal_start_date)
@@ -107,4 +111,4 @@
existing_salary_components.append(d.salary_component)
- return salary_components_details, additional_salary_details
\ No newline at end of file
+ return salary_components_details, additional_salary_details
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
index e166a70..ef844fb 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
@@ -222,7 +222,8 @@
return benefit_amount
-
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_earning_components(doctype, txt, searchfield, start, page_len, filters):
if len(filters) < 2:
return {}
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
index 44763fc..84a97f6 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
@@ -13,6 +13,7 @@
additional_salary = frappe.new_doc('Additional Salary')
additional_salary.employee = self.employee
additional_salary.salary_component = self.salary_component
+ additional_salary.overwrite_salary_structure_amount = 0
additional_salary.amount = self.incentive_amount
additional_salary.payroll_date = self.payroll_date
additional_salary.company = company
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index 1ae3553..8d35a7b 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -30,6 +30,7 @@
).toggleClass('btn-primary', !(frm.doc.employees || []).length);
}
if ((frm.doc.employees || []).length) {
+ frm.page.clear_primary_action();
frm.page.set_primary_action(__('Create Salary Slips'), () => {
frm.save('Submit').then(()=>{
frm.page.clear_primary_action();
@@ -49,13 +50,14 @@
return frappe.call({
doc: frm.doc,
method: 'fill_employee_details',
- callback: function(r) {
- if (r.docs[0].employees){
- frm.save();
- frm.refresh();
- if(r.docs[0].validate_attendance){
- render_employee_attendance(frm, r.message);
- }
+ }).then(r => {
+ if (r.docs && r.docs[0].employees){
+ frm.employees = r.docs[0].employees;
+ frm.dirty();
+ frm.save();
+ frm.refresh();
+ if(r.docs[0].validate_attendance){
+ render_employee_attendance(frm, r.message);
}
}
})
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index e6bb708..554484f 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -539,6 +539,8 @@
if not_submitted_ss:
frappe.msgprint(_("Could not submit some Salary Slips"))
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""
select name from `tabPayroll Entry`
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
index ed0d36c..b8e56ae 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
@@ -26,6 +26,7 @@
additional_salary.amount = self.bonus_amount
additional_salary.payroll_date = self.bonus_payment_date
additional_salary.company = company
+ additional_salary.overwrite_salary_structure_amount = 0
additional_salary.ref_doctype = self.doctype
additional_salary.ref_docname = self.name
additional_salary.submit()
@@ -53,7 +54,7 @@
'employee': self.employee,
'salary_component': self.salary_component,
'payroll_date': self.bonus_payment_date,
- 'company': company,
+ 'company': self.company,
'docstatus': 1,
'ref_doctype': self.doctype,
'ref_docname': self.name
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 663a3ef..27a974a 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -74,9 +74,7 @@
"fieldtype": "Date",
"in_list_view": 1,
"label": "Posting Date",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "employee",
@@ -89,9 +87,7 @@
"oldfieldtype": "Link",
"options": "Employee",
"reqd": 1,
- "search_index": 1,
- "show_days": 1,
- "show_seconds": 1
+ "search_index": 1
},
{
"fetch_from": "employee.employee_name",
@@ -102,9 +98,7 @@
"label": "Employee Name",
"oldfieldname": "employee_name",
"oldfieldtype": "Data",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fetch_from": "employee.department",
@@ -115,20 +109,18 @@
"oldfieldname": "department",
"oldfieldtype": "Link",
"options": "Department",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"depends_on": "eval:doc.designation",
"fetch_from": "employee.designation",
"fieldname": "designation",
- "fieldtype": "Read Only",
+ "fieldtype": "Link",
"label": "Designation",
"oldfieldname": "designation",
"oldfieldtype": "Link",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Designation",
+ "read_only": 1
},
{
"fetch_from": "employee.branch",
@@ -139,16 +131,12 @@
"oldfieldname": "branch",
"oldfieldtype": "Link",
"options": "Branch",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -156,27 +144,21 @@
"fieldtype": "Select",
"label": "Status",
"options": "Draft\nSubmitted\nCancelled",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "journal_entry",
"fieldtype": "Link",
"label": "Journal Entry",
"options": "Journal Entry",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "payroll_entry",
"fieldtype": "Link",
"label": "Payroll Entry",
"options": "Payroll Entry",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "company",
@@ -186,9 +168,7 @@
"label": "Company",
"options": "Company",
"remember_last_selected_value": 1,
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"allow_on_submit": 1,
@@ -197,62 +177,46 @@
"ignore_user_permissions": 1,
"label": "Letter Head",
"options": "Letter Head",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "section_break_10",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"default": "0",
"fieldname": "salary_slip_based_on_timesheet",
"fieldtype": "Check",
"label": "Salary Slip Based on Timesheet",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "start_date",
"fieldtype": "Date",
- "label": "Start Date",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Start Date"
},
{
"fieldname": "end_date",
"fieldtype": "Date",
- "label": "End Date",
- "show_days": 1,
- "show_seconds": 1
+ "label": "End Date"
},
{
"fieldname": "column_break_15",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "salary_structure",
"fieldtype": "Link",
"label": "Salary Structure",
"options": "Salary Structure",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"depends_on": "eval:(!doc.salary_slip_based_on_timesheet)",
"fieldname": "payroll_frequency",
"fieldtype": "Select",
"label": "Payroll Frequency",
- "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily",
- "show_days": 1,
- "show_seconds": 1
+ "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily"
},
{
"fieldname": "total_working_days",
@@ -261,18 +225,14 @@
"oldfieldname": "total_days_in_month",
"oldfieldtype": "Int",
"read_only": 1,
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "leave_without_pay",
"fieldtype": "Float",
"label": "Leave Without Pay",
"oldfieldname": "leave_without_pay",
- "oldfieldtype": "Currency",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Currency"
},
{
"fieldname": "payment_days",
@@ -281,52 +241,38 @@
"oldfieldname": "payment_days",
"oldfieldtype": "Float",
"read_only": 1,
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "hourly_wages",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "timesheets",
"fieldtype": "Table",
"label": "Salary Slip Timesheet",
- "options": "Salary Slip Timesheet",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Salary Slip Timesheet"
},
{
"fieldname": "column_break_20",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "total_working_hours",
"fieldtype": "Float",
"label": "Total Working Hours",
- "print_hide_if_no_value": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide_if_no_value": 1
},
{
"fieldname": "hour_rate",
"fieldtype": "Currency",
"label": "Hour Rate",
"options": "Company:company:default_currency",
- "print_hide_if_no_value": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide_if_no_value": 1
},
{
"fieldname": "section_break_26",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "bank_name",
@@ -334,9 +280,7 @@
"label": "Bank Name",
"oldfieldname": "bank_name",
"oldfieldtype": "Data",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "bank_account_no",
@@ -344,47 +288,34 @@
"label": "Bank Account No.",
"oldfieldname": "bank_account_no",
"oldfieldtype": "Data",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "section_break_32",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"default": "0",
"fieldname": "deduct_tax_for_unclaimed_employee_benefits",
"fieldtype": "Check",
- "label": "Deduct Tax For Unclaimed Employee Benefits",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Deduct Tax For Unclaimed Employee Benefits"
},
{
"default": "0",
"fieldname": "deduct_tax_for_unsubmitted_tax_exemption_proof",
"fieldtype": "Check",
- "label": "Deduct Tax For Unsubmitted Tax Exemption Proof",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Deduct Tax For Unsubmitted Tax Exemption Proof"
},
{
"fieldname": "earning_deduction",
"fieldtype": "Section Break",
"label": "Earning & Deduction",
- "oldfieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Section Break"
},
{
"fieldname": "earning",
"fieldtype": "Column Break",
- "label": "Earning",
"oldfieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -393,17 +324,12 @@
"label": "Earnings",
"oldfieldname": "earning_details",
"oldfieldtype": "Table",
- "options": "Salary Detail",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Salary Detail"
},
{
"fieldname": "deduction",
"fieldtype": "Column Break",
- "label": "Deduction",
"oldfieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -412,16 +338,12 @@
"label": "Deductions",
"oldfieldname": "deduction_details",
"oldfieldtype": "Table",
- "options": "Salary Detail",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Salary Detail"
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
- "oldfieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Section Break"
},
{
"fieldname": "gross_pay",
@@ -430,15 +352,11 @@
"oldfieldname": "gross_pay",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_25",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "total_deduction",
@@ -447,32 +365,24 @@
"oldfieldname": "total_deduction",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"depends_on": "total_loan_repayment",
"fieldname": "loan_repayment",
"fieldtype": "Section Break",
- "label": "Loan repayment",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Loan repayment"
},
{
"fieldname": "loans",
"fieldtype": "Table",
"label": "Employee Loan",
"options": "Salary Slip Loan",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "section_break_43",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"default": "0",
@@ -480,9 +390,7 @@
"fieldtype": "Currency",
"label": "Total Principal Amount",
"options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"default": "0",
@@ -490,15 +398,11 @@
"fieldtype": "Currency",
"label": "Total Interest Amount",
"options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_45",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"default": "0",
@@ -506,16 +410,12 @@
"fieldtype": "Currency",
"label": "Total Loan Repayment",
"options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "net_pay_info",
"fieldtype": "Section Break",
- "label": "net pay info",
- "show_days": 1,
- "show_seconds": 1
+ "label": "net pay info"
},
{
"description": "Gross Pay - Total Deduction - Loan Repayment",
@@ -525,15 +425,11 @@
"oldfieldname": "net_pay",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_53",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"bold": 1,
@@ -541,15 +437,11 @@
"fieldtype": "Currency",
"label": "Rounded Total",
"options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "section_break_55",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"description": "Net Pay (in words) will be visible once you save the Salary Slip.",
@@ -558,9 +450,7 @@
"label": "Total in words",
"oldfieldname": "net_pay_in_words",
"oldfieldtype": "Data",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "amended_from",
@@ -572,9 +462,7 @@
"oldfieldtype": "Data",
"options": "Salary Slip",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fetch_from": "employee.payroll_cost_center",
@@ -583,40 +471,32 @@
"fieldtype": "Link",
"label": "Payroll Cost Center",
"options": "Cost Center",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "mode_of_payment",
"fieldtype": "Select",
"label": "Mode Of Payment",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "absent_days",
"fieldtype": "Float",
"label": "Absent Days",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "unmarked_days",
"fieldtype": "Float",
"hidden": 1,
- "label": "Unmarked days",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Unmarked days"
}
],
"icon": "fa fa-file-text",
"idx": 9,
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 14:42:43.921828",
+ "modified": "2020-07-22 12:41:03.659422",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Slip",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 1e2983e..4ccf564 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -869,10 +869,10 @@
# other taxes and charges on income tax
for d in tax_slab.other_taxes_and_charges:
- if flt(d.min_taxable_income) and flt(d.min_taxable_income) > tax_amount:
+ if flt(d.min_taxable_income) and flt(d.min_taxable_income) > annual_taxable_earning:
continue
- if flt(d.max_taxable_income) and flt(d.max_taxable_income) < tax_amount:
+ if flt(d.max_taxable_income) and flt(d.max_taxable_income) < annual_taxable_earning:
continue
tax_amount += tax_amount * flt(d.percent) / 100
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index be9a2d3..37cd89a 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -105,7 +105,7 @@
#Gross pay calculation based on attendances
gross_pay = 78000 - ((78000 / (days_in_month - no_of_holidays)) * flt(ss.leave_without_pay))
- self.assertEqual(ss.gross_pay, gross_pay)
+ self.assertEqual(flt(ss.gross_pay, 2), flt(gross_pay, 2))
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js
index ca458f9..ad93a2f 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -35,7 +35,9 @@
d.show()
});
- frm.get_field("conditions_and_formula_variable_and_example").$wrapper.append(frm.doc.filters_html).append(help_button)
+ let help_button_wrapper = frm.get_field("conditions_and_formula_variable_and_example").$wrapper;
+ help_button_wrapper.empty();
+ help_button_wrapper.append(frm.doc.filters_html).append(help_button)
frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet)
diff --git a/erpnext/payroll/module_onboarding/payroll/payroll.json b/erpnext/payroll/module_onboarding/payroll/payroll.json
index 7ed786f..b5226b2 100644
--- a/erpnext/payroll/module_onboarding/payroll/payroll.json
+++ b/erpnext/payroll/module_onboarding/payroll/payroll.json
@@ -13,7 +13,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/human-resources/payroll-entry",
"idx": 0,
"is_complete": 0,
- "modified": "2020-06-29 17:00:25.113341",
+ "modified": "2020-07-08 14:06:13.994310",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll",
@@ -44,8 +44,7 @@
"step": "Payroll Settings"
}
],
- "subtitle": "Salary, Compensations and more.",
- "success_message": "The Payroll is all set up!",
- "title": "Let's Setup the Payroll Module. ",
- "user_can_dismiss": 1
+ "subtitle": "Salary, Compensation, and more.",
+ "success_message": "The Payroll Module is all set up!",
+ "title": "Let's Set Up the Payroll Module. "
}
\ No newline at end of file
diff --git a/erpnext/payroll/number_card/total_declaration_submitted/total_declaration_submitted.json b/erpnext/payroll/number_card/total_declaration_submitted/total_declaration_submitted.json
new file mode 100644
index 0000000..fa5739b
--- /dev/null
+++ b/erpnext/payroll/number_card/total_declaration_submitted/total_declaration_submitted.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-22 11:56:34.575627",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Employee Tax Exemption Declaration",
+ "dynamic_filters_json": "[[\"Employee Tax Exemption Declaration\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Employee Tax Exemption Declaration\",\"creation\",\"Timespan\",\"last year\",false],[\"Employee Tax Exemption Declaration\",\"docstatus\",\"=\",\"1\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Declaration Submitted",
+ "modified": "2020-07-22 13:22:46.001099",
+ "modified_by": "Administrator",
+ "module": "Payroll",
+ "name": "Total Declaration Submitted",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git "a/erpnext/payroll/number_card/total_incentive_given\050last_month\051/total_incentive_given\050last_month\051.json" "b/erpnext/payroll/number_card/total_incentive_given\050last_month\051/total_incentive_given\050last_month\051.json"
new file mode 100644
index 0000000..2106706
--- /dev/null
+++ "b/erpnext/payroll/number_card/total_incentive_given\050last_month\051/total_incentive_given\050last_month\051.json"
@@ -0,0 +1,22 @@
+{
+ "aggregate_function_based_on": "incentive_amount",
+ "creation": "2020-07-22 11:56:34.599047",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Employee Incentive",
+ "dynamic_filters_json": "",
+ "filters_json": "[[\"Employee Incentive\",\"docstatus\",\"=\",\"1\",false],[\"Employee Incentive\",\"payroll_date\",\"Timespan\",\"last year\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Incentive Given(Last month)",
+ "modified": "2020-07-23 12:05:26.963616",
+ "modified_by": "Administrator",
+ "module": "Payroll",
+ "name": "Total Incentive Given(Last month)",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git "a/erpnext/payroll/number_card/total_outgoing_salary\050last_month\051/total_outgoing_salary\050last_month\051.json" "b/erpnext/payroll/number_card/total_outgoing_salary\050last_month\051/total_outgoing_salary\050last_month\051.json"
new file mode 100644
index 0000000..44ee722
--- /dev/null
+++ "b/erpnext/payroll/number_card/total_outgoing_salary\050last_month\051/total_outgoing_salary\050last_month\051.json"
@@ -0,0 +1,22 @@
+{
+ "aggregate_function_based_on": "rounded_total",
+ "creation": "2020-07-22 11:56:34.626019",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Salary Slip",
+ "dynamic_filters_json": "[[\"Salary Slip\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Salary Slip\",\"docstatus\",\"=\",\"1\",false],[\"Salary Slip\",\"start_date\",\"Timespan\",\"last month\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Outgoing Salary(Last month)",
+ "modified": "2020-07-22 13:54:14.678954",
+ "modified_by": "Administrator",
+ "module": "Payroll",
+ "name": "Total Outgoing Salary(Last month)",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/payroll/number_card/total_salary_structure/total_salary_structure.json b/erpnext/payroll/number_card/total_salary_structure/total_salary_structure.json
new file mode 100644
index 0000000..030935f
--- /dev/null
+++ b/erpnext/payroll/number_card/total_salary_structure/total_salary_structure.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-22 11:56:34.688843",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Salary Structure",
+ "dynamic_filters_json": "[[\"Salary Structure\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Salary Structure\",\"docstatus\",\"=\",\"1\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Salary Structure",
+ "modified": "2020-07-22 13:24:03.938846",
+ "modified_by": "Administrator",
+ "module": "Payroll",
+ "name": "Total Salary Structure",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/payroll/onboarding_step/create_employee/create_employee.json b/erpnext/payroll/onboarding_step/create_employee/create_employee.json
index 5839ae6..3aa33c6 100644
--- a/erpnext/payroll/onboarding_step/create_employee/create_employee.json
+++ b/erpnext/payroll/onboarding_step/create_employee/create_employee.json
@@ -15,5 +15,5 @@
"reference_document": "Employee",
"show_full_form": 0,
"title": "Create Employee",
- "validate_action": 1
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/payroll/payroll_dashboard/payroll/payroll.json b/erpnext/payroll/payroll_dashboard/payroll/payroll.json
new file mode 100644
index 0000000..fb49d88
--- /dev/null
+++ b/erpnext/payroll/payroll_dashboard/payroll/payroll.json
@@ -0,0 +1,42 @@
+{
+ "cards": [
+ {
+ "card": "Total Declaration Submitted"
+ },
+ {
+ "card": "Total Salary Structure"
+ },
+ {
+ "card": "Total Incentive Given(Last month)"
+ },
+ {
+ "card": "Total Outgoing Salary(Last month)"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "Outgoing Salary",
+ "width": "Full"
+ },
+ {
+ "chart": "Designation Wise Salary(Last Month)",
+ "width": "Half"
+ },
+ {
+ "chart": "Department Wise Salary(Last Month)",
+ "width": "Half"
+ }
+ ],
+ "creation": "2020-07-22 11:56:34.727185",
+ "dashboard_name": "Payroll",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 13:20:18.608969",
+ "modified_by": "Administrator",
+ "module": "Payroll",
+ "name": "Payroll",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/portal/doctype/products_settings/products_settings.py b/erpnext/portal/doctype/products_settings/products_settings.py
index 82afebf..ae7dc68 100644
--- a/erpnext/portal/doctype/products_settings/products_settings.py
+++ b/erpnext/portal/doctype/products_settings/products_settings.py
@@ -11,9 +11,9 @@
class ProductsSettings(Document):
def validate(self):
if self.home_page_is_products:
- website_settings = frappe.get_doc('Website Settings')
- website_settings.home_page = 'products'
- website_settings.save()
+ frappe.db.set_value("Website Settings", None, "home_page", "products")
+ elif frappe.db.get_single_value("Website Settings", "home_page") == 'products':
+ frappe.db.set_value("Website Settings", None, "home_page", "home")
self.validate_field_filters()
self.validate_attribute_filters()
@@ -40,4 +40,3 @@
home_page_is_products = cint(frappe.db.get_single_value('Products Settings', 'home_page_is_products'))
if home_page_is_products:
doc.home_page = 'products'
-
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index 6b6b8c5..f8af30a 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -239,13 +239,12 @@
if exact_match:
data = get_product_info_for_website(exact_match[0])
product_info = data.product_info
+ product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
if not data.cart_settings.show_price:
product_info = None
else:
product_info = None
- product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
-
return {
'next_attribute': next_attribute,
'valid_options_for_attributes': valid_options_for_attributes,
diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py
index 56e4fcd..d6d4469 100644
--- a/erpnext/portal/utils.py
+++ b/erpnext/portal/utils.py
@@ -88,21 +88,30 @@
party.flags.ignore_mandatory = True
party.insert(ignore_permissions=True)
+ alternate_doctype = "Customer" if doctype == "Supplier" else "Supplier"
+
+ if party_exists(alternate_doctype, user):
+ # if user is both customer and supplier, alter fullname to avoid contact name duplication
+ fullname += "-" + doctype
+
+ create_party_contact(doctype, fullname, user, party.name)
+
+ return party
+
+def create_party_contact(doctype, fullname, user, party_name):
contact = frappe.new_doc("Contact")
contact.update({
"first_name": fullname,
"email_id": user
})
- contact.append('links', dict(link_doctype=doctype, link_name=party.name))
+ contact.append('links', dict(link_doctype=doctype, link_name=party_name))
+ contact.append('email_ids', dict(email_id=user))
contact.flags.ignore_mandatory = True
contact.insert(ignore_permissions=True)
- return party
-
-
def party_exists(doctype, user):
+ # check if contact exists against party and if it is linked to the doctype
contact_name = frappe.db.get_value("Contact", {"email_id": user})
-
if contact_name:
contact = frappe.get_doc('Contact', contact_name)
doctypes = [d.link_doctype for d in contact.links]
diff --git a/erpnext/projects/dashboard_chart/project_summary/project_summary.json b/erpnext/projects/dashboard_chart/project_summary/project_summary.json
new file mode 100644
index 0000000..157ee1b
--- /dev/null
+++ b/erpnext/projects/dashboard_chart/project_summary/project_summary.json
@@ -0,0 +1,24 @@
+{
+ "chart_name": "Project Summary",
+ "chart_type": "Report",
+ "creation": "2020-07-20 20:17:16.363681",
+ "custom_options": "{\"type\": \"bar\", \"colors\": [\"#fc4f51\", \"#78d6ff\", \"#7575ff\"], \"axisOptions\": { \"shortenYAxisNumbers\": 1}, \"barOptions\": { \"stacked\": 1 }}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}",
+ "filters_json": "{\"status\":\"Open\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 17:16:39.627076",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Project Summary",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Project Summary",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/projects/dashboard_fixtures.py b/erpnext/projects/dashboard_fixtures.py
deleted file mode 100644
index d89ffe9..0000000
--- a/erpnext/projects/dashboard_fixtures.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-import json
-from frappe import _
-
-def get_company_for_dashboards():
- company = frappe.defaults.get_defaults().company
- if company:
- return company
- else:
- company_list = frappe.get_list("Company")
- if company_list:
- return company_list[0].name
- return None
-
-def get_data():
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(),
- })
-
-def get_dashboards():
- return [{
- "doctype": "Dashboard",
- "name": "Project",
- "dashboard_name": "Project",
- "charts": [
- { "chart": "Project Summary", "width": "Full" }
- ]
- }]
-
-def get_charts():
- company = frappe.get_doc("Company", get_company_for_dashboards())
-
- return [
- {
- 'doctype': 'Dashboard Chart',
- 'name': 'Project Summary',
- 'chart_name': _('Project Summary'),
- 'chart_type': 'Report',
- 'report_name': 'Project Summary',
- 'is_public': 1,
- 'is_custom': 1,
- 'filters_json': json.dumps({"company": company.name, "status": "Open"}),
- 'type': 'Bar',
- 'custom_options': '{"type": "bar", "colors": ["#fc4f51", "#78d6ff", "#7575ff"], "axisOptions": { "shortenYAxisNumbers": 1}, "barOptions": { "stacked": 1 }}',
- }
- ]
\ No newline at end of file
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index afdb5b7..5bbd29c 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -238,6 +238,8 @@
"row_template": "templates/includes/projects/project_row.html"
}
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_users_for_project(doctype, txt, searchfield, start, page_len, filters):
conditions = []
return frappe.db.sql("""select name, concat_ws(' ', first_name, middle_name, last_name)
@@ -471,7 +473,7 @@
from frappe.desk.doctype.kanban_board.kanban_board import quick_kanban_board
if not frappe.db.exists('Kanban Board', project):
- quick_kanban_board('Task', project, 'status')
+ quick_kanban_board('Task', project, 'status', project)
return True
diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py
index 06c62b6..0c4f6f1 100644
--- a/erpnext/projects/doctype/project/test_project.py
+++ b/erpnext/projects/doctype/project/test_project.py
@@ -7,7 +7,7 @@
test_records = frappe.get_test_records('Project')
test_ignore = ["Sales Order"]
-from erpnext.projects.doctype.project_template.test_project_template import get_project_template
+from erpnext.projects.doctype.project_template.test_project_template import get_project_template, make_project_template
from erpnext.projects.doctype.project.project import set_project_status
from frappe.utils import getdate
@@ -43,4 +43,24 @@
expected_start_date = '2019-01-01'
)).insert()
+ return project
+
+def make_project(args):
+ args = frappe._dict(args)
+ if args.project_template_name:
+ template = make_project_template(args.project_template_name)
+ else:
+ template = get_project_template()
+
+ project = frappe.get_doc(dict(
+ doctype = 'Project',
+ project_name = args.project_name,
+ status = 'Open',
+ project_template = template.name,
+ expected_start_date = args.start_date
+ ))
+
+ if not frappe.db.exists("Project", args.project_name):
+ project.insert()
+
return project
\ No newline at end of file
diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py
index efcb2ea..2c5831a 100644
--- a/erpnext/projects/doctype/project_template/test_project_template.py
+++ b/erpnext/projects/doctype/project_template/test_project_template.py
@@ -26,4 +26,23 @@
]
)).insert()
- return frappe.get_doc('Project Template', 'Test Project Template')
\ No newline at end of file
+ return frappe.get_doc('Project Template', 'Test Project Template')
+
+def make_project_template(project_template_name, project_tasks=[]):
+ if not frappe.db.exists('Project Template', project_template_name):
+ frappe.get_doc(dict(
+ doctype = 'Project Template',
+ name = project_template_name,
+ tasks = project_tasks or [
+ dict(subject='Task 1', description='Task 1 description',
+ start=0, duration=3),
+ dict(subject='Task 2', description='Task 2 description',
+ start=0, duration=2),
+ dict(subject='Task 3', description='Task 3 description',
+ start=2, duration=4),
+ dict(subject='Task 4', description='Task 4 description',
+ start=3, duration=2),
+ ]
+ )).insert()
+
+ return frappe.get_doc('Project Template', project_template_name)
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js
index 5719276..8c6a9cf 100644
--- a/erpnext/projects/doctype/task/task.js
+++ b/erpnext/projects/doctype/task/task.js
@@ -3,55 +3,42 @@
frappe.provide("erpnext.projects");
-cur_frm.add_fetch("project", "company", "company");
-
frappe.ui.form.on("Task", {
- onload: function(frm) {
- frm.set_query("task", "depends_on", function() {
- var filters = {
+ setup: function (frm) {
+ frm.set_query("project", function () {
+ return {
+ query: "erpnext.projects.doctype.task.task.get_project"
+ }
+ });
+
+ frm.make_methods = {
+ 'Timesheet': () => frappe.model.open_mapped_doc({
+ method: 'erpnext.projects.doctype.task.task.make_timesheet',
+ frm: frm
+ })
+ }
+ },
+
+ onload: function (frm) {
+ frm.set_query("task", "depends_on", function () {
+ let filters = {
name: ["!=", frm.doc.name]
};
- if(frm.doc.project) filters["project"] = frm.doc.project;
+ if (frm.doc.project) filters["project"] = frm.doc.project;
return {
filters: filters
};
})
- },
- refresh: function(frm) {
- frm.fields_dict['parent_task'].get_query = function () {
+ frm.set_query("parent_task", function () {
+ let filters = {
+ "is_group": 1
+ };
+ if (frm.doc.project) filters["project"] = frm.doc.project;
return {
- filters: {
- "is_group": 1,
- }
+ filters: filters
}
- }
-
- if (!frm.doc.is_group) {
- if (!frm.is_new()) {
- if (frappe.model.can_read("Timesheet")) {
- frm.add_custom_button(__("Timesheet"), () => {
- frappe.route_options = { "project": frm.doc.project, "task": frm.doc.name }
- frappe.set_route("List", "Timesheet");
- }, __("View"), true);
- }
-
- if (frappe.model.can_read("Expense Claim")) {
- frm.add_custom_button(__("Expense Claims"), () => {
- frappe.route_options = { "project": frm.doc.project, "task": frm.doc.name };
- frappe.set_route("List", "Expense Claim");
- }, __("View"), true);
- }
- }
- }
- },
-
- setup: function(frm) {
- frm.fields_dict.project.get_query = function() {
- return {
- query: "erpnext.projects.doctype.task.task.get_project"
- }
- };
+ });
},
is_group: function (frm) {
@@ -69,12 +56,8 @@
})
},
- validate: function(frm) {
+ validate: function (frm) {
frm.doc.project && frappe.model.remove_from_locals("Project",
frm.doc.project);
- },
-
+ }
});
-
-cur_frm.add_fetch('task', 'subject', 'subject');
-cur_frm.add_fetch('task', 'project', 'project');
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index f4b3d3e..27f1a71 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -183,7 +183,8 @@
{
"fieldname": "progress",
"fieldtype": "Percent",
- "label": "% Progress"
+ "label": "% Progress",
+ "no_copy": 1
},
{
"default": "0",
@@ -324,6 +325,7 @@
"options": "Department"
},
{
+ "fetch_from": "project.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
@@ -356,6 +358,7 @@
"fieldname": "completed_by",
"fieldtype": "Link",
"label": "Completed By",
+ "no_copy": 1,
"options": "User"
}
],
@@ -364,7 +367,7 @@
"is_tree": 1,
"links": [],
"max_attachments": 5,
- "modified": "2020-03-18 18:08:44.153211",
+ "modified": "2020-07-03 12:36:04.960457",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 1cb2c50..fb84094 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -7,10 +7,11 @@
import frappe
from frappe import _, throw
-from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today
+from frappe.desk.form.assign_to import clear, close_all_assignments
+from frappe.model.mapper import get_mapped_doc
+from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today, flt
from frappe.utils.nestedset import NestedSet
-from frappe.desk.form.assign_to import close_all_assignments, clear
-from frappe.utils import date_diff
+
class CircularReferenceError(frappe.ValidationError): pass
class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass
@@ -62,10 +63,10 @@
close_all_assignments(self.doctype, self.name)
def validate_progress(self):
- if (self.progress or 0) > 100:
+ if flt(self.progress or 0) > 100:
frappe.throw(_("Progress % for a task cannot be more than 100."))
- if self.progress == 100:
+ if flt(self.progress) == 100:
self.status = 'Completed'
if self.status == 'Completed':
@@ -174,6 +175,9 @@
self.update_nsm_model()
+ def after_delete(self):
+ self.update_project()
+
def update_status(self):
if self.status not in ('Cancelled', 'Completed') and self.exp_end_date:
from datetime import datetime
@@ -188,6 +192,8 @@
return child_tasks
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_project(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
return frappe.db.sql(""" select name from `tabProject`
@@ -219,6 +225,26 @@
continue
frappe.get_doc("Task", task.name).update_status()
+
+@frappe.whitelist()
+def make_timesheet(source_name, target_doc=None, ignore_permissions=False):
+ def set_missing_values(source, target):
+ target.append("time_logs", {
+ "hours": source.actual_time,
+ "completed": source.status == "Completed",
+ "project": source.project,
+ "task": source.name
+ })
+
+ doclist = get_mapped_doc("Task", source_name, {
+ "Task": {
+ "doctype": "Timesheet"
+ }
+ }, target_doc, postprocess=set_missing_values, ignore_permissions=ignore_permissions)
+
+ return doclist
+
+
@frappe.whitelist()
def get_children(doctype, parent, task=None, project=None, is_root=False):
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 03b67b1..a5ce44d 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -140,52 +140,6 @@
settings.ignore_employee_time_overlap = initial_setting
settings.save()
- def test_timesheet_std_working_hours(self):
- emp = make_employee("test_employee_6@salary.com")
-
- company = frappe.get_doc('Company', "_Test Company")
- company.standard_working_hours = 8
- company.save()
-
- timesheet = frappe.new_doc("Timesheet")
- timesheet.employee = emp
- timesheet.company = '_Test Company'
- timesheet.append(
- 'time_logs',
- {
- "activity_type": "_Test Activity Type",
- "from_time": now_datetime(),
- "to_time": now_datetime() + datetime.timedelta(days= 4)
- }
- )
- timesheet.save()
-
- ts = frappe.get_doc('Timesheet', timesheet.name)
- self.assertEqual(ts.total_hours, 32)
- ts.submit()
- ts.cancel()
-
- company = frappe.get_doc('Company', "_Test Company")
- company.standard_working_hours = 0
- company.save()
-
- timesheet = frappe.new_doc("Timesheet")
- timesheet.employee = emp
- timesheet.company = '_Test Company'
- timesheet.append(
- 'time_logs',
- {
- "activity_type": "_Test Activity Type",
- "from_time": now_datetime(),
- "to_time": now_datetime() + datetime.timedelta(days= 4)
- }
- )
- timesheet.save()
-
- ts = frappe.get_doc('Timesheet', timesheet.name)
- self.assertEqual(ts.total_hours, 96)
- ts.submit()
- ts.cancel()
def make_salary_structure_for_timesheet(employee):
salary_structure_name = "Timesheet Salary Structure Test"
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index defc18b..5de2930 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -162,19 +162,11 @@
to_time: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
- var time_diff = (moment(child.to_time).diff(moment(child.from_time),"seconds")) / ( 60 * 60 * 24);
- var std_working_hours = 0;
if(frm._setting_hours) return;
var hours = moment(child.to_time).diff(moment(child.from_time), "seconds") / 3600;
- std_working_hours = time_diff * frappe.working_hours;
-
- if (std_working_hours < hours && std_working_hours > 0) {
- frappe.model.set_value(cdt, cdn, "hours", std_working_hours);
- } else {
- frappe.model.set_value(cdt, cdn, "hours", hours);
- }
+ frappe.model.set_value(cdt, cdn, "hours", hours);
},
time_logs_add: function(frm) {
@@ -236,23 +228,12 @@
let d = moment(child.from_time);
if(child.hours) {
- var time_diff = (moment(child.to_time).diff(moment(child.from_time),"seconds")) / (60 * 60 * 24);
- var std_working_hours = 0;
- var hours = moment(child.to_time).diff(moment(child.from_time), "seconds") / 3600;
-
- std_working_hours = time_diff * frappe.working_hours;
-
- if (std_working_hours < hours && std_working_hours > 0) {
- frappe.model.set_value(cdt, cdn, "hours", std_working_hours);
- frappe.model.set_value(cdt, cdn, "to_time", d.add(hours, "hours").format(frappe.defaultDatetimeFormat));
- } else {
- d.add(child.hours, "hours");
- frm._setting_hours = true;
- frappe.model.set_value(cdt, cdn, "to_time",
- d.format(frappe.defaultDatetimeFormat)).then(() => {
- frm._setting_hours = false;
- });
- }
+ d.add(child.hours, "hours");
+ frm._setting_hours = true;
+ frappe.model.set_value(cdt, cdn, "to_time",
+ d.format(frappe.defaultDatetimeFormat)).then(() => {
+ frm._setting_hours = false;
+ });
}
};
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index e908216..9e807f7 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -24,7 +24,6 @@
self.set_status()
self.validate_dates()
self.validate_time_logs()
- self.calculate_std_hours()
self.update_cost()
self.calculate_total_amounts()
self.calculate_percentage_billed()
@@ -91,17 +90,6 @@
self.start_date = getdate(start_date)
self.end_date = getdate(end_date)
- def calculate_std_hours(self):
- std_working_hours = frappe.get_value("Company", self.company, 'standard_working_hours')
-
- for time in self.time_logs:
- if time.from_time and time.to_time:
- if flt(std_working_hours) and date_diff(time.to_time, time.from_time):
- time.hours = flt(std_working_hours) * date_diff(time.to_time, time.from_time)
- else:
- if not time.hours:
- time.hours = time_diff_in_hours(time.to_time, time.from_time)
-
def before_cancel(self):
self.set_status()
@@ -226,6 +214,7 @@
and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1)
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_timesheet(doctype, txt, searchfield, start, page_len, filters):
if not filters: filters = {}
diff --git a/erpnext/projects/doctype/timesheet/timesheet_dashboard.py b/erpnext/projects/doctype/timesheet/timesheet_dashboard.py
new file mode 100644
index 0000000..acff97a
--- /dev/null
+++ b/erpnext/projects/doctype/timesheet/timesheet_dashboard.py
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'time_sheet',
+ 'transactions': [
+ {
+ 'label': _('References'),
+ 'items': ['Sales Invoice', 'Salary Slip']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/projects/projects_dashboard/project/project.json b/erpnext/projects/projects_dashboard/project/project.json
new file mode 100644
index 0000000..f7824ce
--- /dev/null
+++ b/erpnext/projects/projects_dashboard/project/project.json
@@ -0,0 +1,21 @@
+{
+ "cards": [],
+ "charts": [
+ {
+ "chart": "Project Summary",
+ "width": "Full"
+ }
+ ],
+ "creation": "2020-07-20 20:17:16.397373",
+ "dashboard_name": "Project",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 0,
+ "is_standard": 1,
+ "modified": "2020-07-22 17:17:03.780625",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Project",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/projects/utils.py b/erpnext/projects/utils.py
index d0d88eb..c39f908 100644
--- a/erpnext/projects/utils.py
+++ b/erpnext/projects/utils.py
@@ -7,6 +7,7 @@
import frappe
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def query_task(doctype, txt, searchfield, start, page_len, filters):
from frappe.desk.reportview import build_match_conditions
diff --git a/erpnext/public/css/pos.css b/erpnext/public/css/pos.css
index 613a5ff..e80e3ed 100644
--- a/erpnext/public/css/pos.css
+++ b/erpnext/public/css/pos.css
@@ -1,179 +1,216 @@
-[data-route="point-of-sale"] .layout-main-section-wrapper {
- margin-bottom: 0;
-}
-[data-route="point-of-sale"] .pos-items-wrapper {
- max-height: calc(100vh - 210px);
-}
-.pos {
- padding: 15px;
-}
-.list-item {
- min-height: 40px;
- height: auto;
-}
-.cart-container {
- padding: 0 15px;
- display: inline-block;
- width: 39%;
- vertical-align: top;
-}
-.item-container {
- padding: 0 15px;
- display: inline-block;
- width: 60%;
- vertical-align: top;
-}
-.search-field {
- width: 60%;
-}
-.search-field input::placeholder {
- font-size: 12px;
-}
-.item-group-field {
- width: 40%;
- margin-left: 15px;
-}
-.cart-wrapper {
- margin-bottom: 12px;
-}
-.cart-wrapper .list-item__content:not(:first-child) {
- justify-content: flex-end;
-}
-.cart-wrapper .list-item--head .list-item__content:nth-child(2) {
- flex: 1.5;
-}
-.cart-items {
- height: 150px;
- overflow: auto;
-}
-.cart-items .list-item.current-item {
- background-color: #fffce7;
-}
-.cart-items .list-item.current-item.qty input {
- border: 1px solid #5E64FF;
- font-weight: bold;
-}
-.cart-items .list-item.current-item.disc .discount {
- font-weight: bold;
-}
-.cart-items .list-item.current-item.rate .rate {
- font-weight: bold;
-}
-.cart-items .list-item .quantity {
- flex: 1.5;
-}
-.cart-items input {
- text-align: right;
- height: 22px;
- font-size: 12px;
-}
-.fields {
- display: flex;
-}
-.pos-items-wrapper {
- max-height: 480px;
- overflow-y: auto;
-}
-.pos-items {
- overflow: hidden;
-}
-.pos-item-wrapper {
- display: flex;
- flex-direction: column;
- position: relative;
- width: 25%;
-}
-.image-view-container {
- display: block;
-}
-.image-view-container .image-field {
- height: auto;
-}
-.empty-state {
- height: 100%;
- position: relative;
-}
-.empty-state span {
- position: absolute;
- color: #8D99A6;
- font-size: 12px;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
-}
-@keyframes yellow-fade {
- 0% {
- background-color: #fffce7;
- }
- 100% {
- background-color: transparent;
- }
-}
-.highlight {
- animation: yellow-fade 1s ease-in 1;
-}
-input[type=number]::-webkit-inner-spin-button,
-input[type=number]::-webkit-outer-spin-button {
- -webkit-appearance: none;
- margin: 0;
-}
-.number-pad {
- border-collapse: collapse;
- cursor: pointer;
- display: table;
-}
-.num-row {
- display: table-row;
-}
-.num-col {
- display: table-cell;
- border: 1px solid #d1d8dd;
-}
-.num-col > div {
- width: 50px;
- height: 50px;
- text-align: center;
- line-height: 50px;
-}
-.num-col.active {
- background-color: #fffce7;
-}
-.num-col.brand-primary {
- background-color: #5E64FF;
- color: #ffffff;
-}
-.discount-amount .discount-inputs {
- display: flex;
- flex-direction: column;
- padding: 15px 0;
-}
-.discount-amount input:first-child {
- margin-bottom: 10px;
-}
-.taxes-and-totals {
- border-top: 1px solid #d1d8dd;
-}
-.taxes-and-totals .taxes {
- display: flex;
- flex-direction: column;
- padding: 15px 0;
- align-items: flex-end;
-}
-.taxes-and-totals .taxes > div:first-child {
- margin-bottom: 10px;
-}
-.grand-total {
- border-top: 1px solid #d1d8dd;
-}
-.grand-total .list-item {
- height: 60px;
-}
-.grand-total .grand-total-value {
- font-size: 18px;
-}
-.rounded-total-value {
- font-size: 18px;
-}
-.quantity-total {
- font-size: 18px;
-}
+[data-route="point-of-sale"] .layout-main-section { border: none; font-size: 12px; }
+[data-route="point-of-sale"] .layout-main-section-wrapper { margin-bottom: 0; }
+[data-route="point-of-sale"] .pos-items-wrapper { max-height: calc(100vh - 210px); }
+:root { --border-color: #d1d8dd; --text-color: #8d99a6; --primary: #5e64ff; }
+[data-route="point-of-sale"] .flex { display: flex; }
+[data-route="point-of-sale"] .grid { display: grid; }
+[data-route="point-of-sale"] .absolute { position: absolute; }
+[data-route="point-of-sale"] .relative { position: relative; }
+[data-route="point-of-sale"] .abs-center { top: 50%; left: 50%; transform: translate(-50%, -50%); }
+[data-route="point-of-sale"] .inline { display: inline; }
+[data-route="point-of-sale"] .float-right { float: right; }
+[data-route="point-of-sale"] .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
+[data-route="point-of-sale"] .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+[data-route="point-of-sale"] .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
+[data-route="point-of-sale"] .grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
+[data-route="point-of-sale"] .grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
+[data-route="point-of-sale"] .grid-cols-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); }
+[data-route="point-of-sale"] .gap-2 { grid-gap: 0.5rem; gap: 0.5rem; }
+[data-route="point-of-sale"] .gap-4 { grid-gap: 1rem; gap: 1rem; }
+[data-route="point-of-sale"] .gap-6 { grid-gap: 1.25rem; gap: 1.25rem; }
+[data-route="point-of-sale"] .gap-8 { grid-gap: 1.5rem; gap: 1.5rem; }
+[data-route="point-of-sale"] .row-gap-2 { grid-row-gap: 0.5rem; row-gap: 0.5rem; }
+[data-route="point-of-sale"] .col-gap-4 { grid-column-gap: 1rem; column-gap: 1rem; }
+[data-route="point-of-sale"] .col-span-2 { grid-column: span 2 / span 2; }
+[data-route="point-of-sale"] .col-span-3 { grid-column: span 3 / span 3; }
+[data-route="point-of-sale"] .col-span-4 { grid-column: span 4 / span 4; }
+[data-route="point-of-sale"] .col-span-6 { grid-column: span 6 / span 6; }
+[data-route="point-of-sale"] .col-span-10 { grid-column: span 10 / span 10; }
+[data-route="point-of-sale"] .row-span-2 { grid-row: span 2 / span 2; }
+[data-route="point-of-sale"] .grid-auto-row { grid-auto-rows: 5.5rem; }
+[data-route="point-of-sale"] .d-none { display: none; }
+[data-route="point-of-sale"] .flex-wrap { flex-wrap: wrap; }
+[data-route="point-of-sale"] .flex-row { flex-direction: row; }
+[data-route="point-of-sale"] .flex-col { flex-direction: column; }
+[data-route="point-of-sale"] .flex-row-rev { flex-direction: row-reverse; }
+[data-route="point-of-sale"] .flex-col-rev { flex-direction: column-reverse; }
+[data-route="point-of-sale"] .flex-1 { flex: 1 1 0%; }
+[data-route="point-of-sale"] .items-center { align-items: center; }
+[data-route="point-of-sale"] .items-end { align-items: flex-end; }
+[data-route="point-of-sale"] .f-grow-1 { flex-grow: 1; }
+[data-route="point-of-sale"] .f-grow-2 { flex-grow: 2; }
+[data-route="point-of-sale"] .f-grow-3 { flex-grow: 3; }
+[data-route="point-of-sale"] .f-grow-4 { flex-grow: 4; }
+[data-route="point-of-sale"] .f-shrink-0 { flex-shrink: 0; }
+[data-route="point-of-sale"] .f-shrink-1 { flex-shrink: 1; }
+[data-route="point-of-sale"] .f-shrink-2 { flex-shrink: 2; }
+[data-route="point-of-sale"] .f-shrink-3 { flex-shrink: 3; }
+[data-route="point-of-sale"] .shadow { box-shadow: 0 0px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 2px 0 rgba(0, 0, 0, 0.06); }
+[data-route="point-of-sale"] .shadow-sm { box-shadow: 0 0.5px 3px 0 rgba(0, 0, 0, 0.125); }
+[data-route="point-of-sale"] .shadow-inner { box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1); }
+[data-route="point-of-sale"] .rounded { border-radius: 0.3rem; }
+[data-route="point-of-sale"] .rounded-b { border-bottom-left-radius: 0.3rem; border-bottom-right-radius: 0.3rem; }
+[data-route="point-of-sale"] .p-8 { padding: 2rem; }
+[data-route="point-of-sale"] .p-16 { padding: 4rem; }
+[data-route="point-of-sale"] .p-32 { padding: 8rem; }
+[data-route="point-of-sale"] .p-6 { padding: 1.5rem; }
+[data-route="point-of-sale"] .p-4 { padding: 1rem; }
+[data-route="point-of-sale"] .p-3 { padding: 0.75rem; }
+[data-route="point-of-sale"] .p-2 { padding: 0.5rem; }
+[data-route="point-of-sale"] .m-8 { margin: 2rem; }
+[data-route="point-of-sale"] .p-1 { padding: 0.25rem; }
+[data-route="point-of-sale"] .pr-0 { padding-right: 0rem; }
+[data-route="point-of-sale"] .pl-0 { padding-left: 0rem; }
+[data-route="point-of-sale"] .pt-0 { padding-top: 0rem; }
+[data-route="point-of-sale"] .pb-0 { padding-bottom: 0rem; }
+[data-route="point-of-sale"] .mr-0 { margin-right: 0rem; }
+[data-route="point-of-sale"] .ml-0 { margin-left: 0rem; }
+[data-route="point-of-sale"] .mt-0 { margin-top: 0rem; }
+[data-route="point-of-sale"] .mb-0 { margin-bottom: 0rem; }
+[data-route="point-of-sale"] .pr-2 { padding-right: 0.5rem; }
+[data-route="point-of-sale"] .pl-2 { padding-left: 0.5rem; }
+[data-route="point-of-sale"] .pt-2 { padding-top: 0.5rem; }
+[data-route="point-of-sale"] .pb-2 { padding-bottom: 0.5rem; }
+[data-route="point-of-sale"] .pr-3 { padding-right: 0.75rem; }
+[data-route="point-of-sale"] .pl-3 { padding-left: 0.75rem; }
+[data-route="point-of-sale"] .pt-3 { padding-top: 0.75rem; }
+[data-route="point-of-sale"] .pb-3 { padding-bottom: 0.75rem; }
+[data-route="point-of-sale"] .pr-4 { padding-right: 1rem; }
+[data-route="point-of-sale"] .pl-4 { padding-left: 1rem; }
+[data-route="point-of-sale"] .pt-4 { padding-top: 1rem; }
+[data-route="point-of-sale"] .pb-4 { padding-bottom: 1rem; }
+[data-route="point-of-sale"] .mr-4 { margin-right: 1rem; }
+[data-route="point-of-sale"] .ml-4 { margin-left: 1rem; }
+[data-route="point-of-sale"] .mt-4 { margin-top: 1rem; }
+[data-route="point-of-sale"] .mb-4 { margin-bottom: 1rem; }
+[data-route="point-of-sale"] .mr-2 { margin-right: 0.5rem; }
+[data-route="point-of-sale"] .ml-2 { margin-left: 0.5rem; }
+[data-route="point-of-sale"] .mt-2 { margin-top: 0.5rem; }
+[data-route="point-of-sale"] .mb-2 { margin-bottom: 0.5rem; }
+[data-route="point-of-sale"] .mr-1 { margin-right: 0.25rem; }
+[data-route="point-of-sale"] .ml-1 { margin-left: 0.25rem; }
+[data-route="point-of-sale"] .mt-1 { margin-top: 0.25rem; }
+[data-route="point-of-sale"] .mb-1 { margin-bottom: 0.25rem; }
+[data-route="point-of-sale"] .mr-auto { margin-right: auto; }
+[data-route="point-of-sale"] .ml-auto { margin-left: auto; }
+[data-route="point-of-sale"] .mt-auto { margin-top: auto; }
+[data-route="point-of-sale"] .mb-auto { margin-bottom: auto; }
+[data-route="point-of-sale"] .pr-6 { padding-right: 1.5rem; }
+[data-route="point-of-sale"] .pl-6 { padding-left: 1.5rem; }
+[data-route="point-of-sale"] .pt-6 { padding-top: 1.5rem; }
+[data-route="point-of-sale"] .pb-6 { padding-bottom: 1.5rem; }
+[data-route="point-of-sale"] .mr-6 { margin-right: 1.5rem; }
+[data-route="point-of-sale"] .ml-6 { margin-left: 1.5rem; }
+[data-route="point-of-sale"] .mt-6 { margin-top: 1.5rem; }
+[data-route="point-of-sale"] .mb-6 { margin-bottom: 1.5rem; }
+[data-route="point-of-sale"] .mr-8 { margin-right: 2rem; }
+[data-route="point-of-sale"] .ml-8 { margin-left: 2rem; }
+[data-route="point-of-sale"] .mt-8 { margin-top: 2rem; }
+[data-route="point-of-sale"] .mb-8 { margin-bottom: 2rem; }
+[data-route="point-of-sale"] .pr-8 { padding-right: 2rem; }
+[data-route="point-of-sale"] .pl-8 { padding-left: 2rem; }
+[data-route="point-of-sale"] .pt-8 { padding-top: 2rem; }
+[data-route="point-of-sale"] .pb-8 { padding-bottom: 2rem; }
+[data-route="point-of-sale"] .pr-16 { padding-right: 4rem; }
+[data-route="point-of-sale"] .pl-16 { padding-left: 4rem; }
+[data-route="point-of-sale"] .pt-16 { padding-top: 4rem; }
+[data-route="point-of-sale"] .pb-16 { padding-bottom: 4rem; }
+[data-route="point-of-sale"] .w-full { width: 100%; }
+[data-route="point-of-sale"] .h-full { height: 100%; }
+[data-route="point-of-sale"] .w-quarter { width: 25%; }
+[data-route="point-of-sale"] .w-half { width: 50%; }
+[data-route="point-of-sale"] .w-66 { width: 66.66%; }
+[data-route="point-of-sale"] .w-33 { width: 33.33%; }
+[data-route="point-of-sale"] .w-60 { width: 60%; }
+[data-route="point-of-sale"] .w-40 { width: 40%; }
+[data-route="point-of-sale"] .w-fit { width: fit-content; }
+[data-route="point-of-sale"] .w-6 { width: 2rem; }
+[data-route="point-of-sale"] .h-6 { min-height: 2rem; height: 2rem; }
+[data-route="point-of-sale"] .w-8 { width: 2.5rem; }
+[data-route="point-of-sale"] .h-8 { min-height: 2.5rem; height: 2.5rem; }
+[data-route="point-of-sale"] .w-10 { width: 3rem; }
+[data-route="point-of-sale"] .h-10 { min-height:3rem; height: 3rem; }
+[data-route="point-of-sale"] .h-12 { min-height: 3.3rem; height: 3.3rem; }
+[data-route="point-of-sale"] .w-12 { width: 3.3rem; }
+[data-route="point-of-sale"] .h-14 { min-height: 4.2rem; height: 4.2rem; }
+[data-route="point-of-sale"] .h-16 { min-height: 4.6rem; height: 4.6rem; }
+[data-route="point-of-sale"] .h-18 { min-height: 5rem; height: 5rem; }
+[data-route="point-of-sale"] .w-18 { width: 5.4rem; }
+[data-route="point-of-sale"] .w-24 { width: 7.2rem; }
+[data-route="point-of-sale"] .w-26 { width: 8.4rem; }
+[data-route="point-of-sale"] .h-24 { min-height: 7.2rem; height: 7.2rem; }
+[data-route="point-of-sale"] .h-32 { min-height: 9.6rem; height: 9.6rem; }
+[data-route="point-of-sale"] .w-46 { width: 15rem; }
+[data-route="point-of-sale"] .h-46 { min-height:15rem; height: 15rem; }
+[data-route="point-of-sale"] .h-100 { height: 100vh; }
+[data-route="point-of-sale"] .mx-h-70 { max-height: 67rem; }
+[data-route="point-of-sale"] .border-grey-300 { border-color: #e2e8f0; }
+[data-route="point-of-sale"] .border-grey { border: 1px solid #d1d8dd; }
+[data-route="point-of-sale"] .border-white { border: 1px solid #fff; }
+[data-route="point-of-sale"] .border-b-grey { border-bottom: 1px solid #d1d8dd; }
+[data-route="point-of-sale"] .border-t-grey { border-top: 1px solid #d1d8dd; }
+[data-route="point-of-sale"] .border-r-grey { border-right: 1px solid #d1d8dd; }
+[data-route="point-of-sale"] .text-dark-grey { color: #5f5f5f; }
+[data-route="point-of-sale"] .text-grey { color: #8d99a6; }
+[data-route="point-of-sale"] .text-grey-100 { color: #d1d8dd; }
+[data-route="point-of-sale"] .text-grey-200 { color: #a0aec0; }
+[data-route="point-of-sale"] .bg-green-200 { background-color: #c6f6d5; }
+[data-route="point-of-sale"] .text-bold { font-weight: bold; }
+[data-route="point-of-sale"] .italic { font-style: italic; }
+[data-route="point-of-sale"] .font-weight-450 { font-weight: 450; }
+[data-route="point-of-sale"] .justify-around { justify-content: space-around; }
+[data-route="point-of-sale"] .justify-between { justify-content: space-between; }
+[data-route="point-of-sale"] .justify-center { justify-content: center; }
+[data-route="point-of-sale"] .justify-end { justify-content: flex-end; }
+[data-route="point-of-sale"] .bg-white { background-color: white; }
+[data-route="point-of-sale"] .bg-light-grey { background-color: #f0f4f7; }
+[data-route="point-of-sale"] .bg-grey-100 { background-color: #f7fafc; }
+[data-route="point-of-sale"] .bg-grey-200 { background-color: #edf2f7; }
+[data-route="point-of-sale"] .bg-grey { background-color: #f4f5f6; }
+[data-route="point-of-sale"] .text-center { text-align: center; }
+[data-route="point-of-sale"] .text-right { text-align: right; }
+[data-route="point-of-sale"] .text-sm { font-size: 1rem; }
+[data-route="point-of-sale"] .text-md-0 { font-size: 1.25rem; }
+[data-route="point-of-sale"] .text-md { font-size: 1.4rem; }
+[data-route="point-of-sale"] .text-lg { font-size: 1.6rem; }
+[data-route="point-of-sale"] .text-xl { font-size: 2.2rem; }
+[data-route="point-of-sale"] .text-2xl { font-size: 2.8rem; }
+[data-route="point-of-sale"] .text-2-5xl { font-size: 3rem; }
+[data-route="point-of-sale"] .text-3xl { font-size: 3.8rem; }
+[data-route="point-of-sale"] .text-6xl { font-size: 4.8rem; }
+[data-route="point-of-sale"] .line-through { text-decoration: line-through; }
+[data-route="point-of-sale"] .text-primary { color: #5e64ff; }
+[data-route="point-of-sale"] .text-white { color: #fff; }
+[data-route="point-of-sale"] .text-green-500 { color: #48bb78; }
+[data-route="point-of-sale"] .bg-primary { background-color: #5e64ff; }
+[data-route="point-of-sale"] .border-primary { border-color: #5e64ff; }
+[data-route="point-of-sale"] .text-danger { color: #e53e3e; }
+[data-route="point-of-sale"] .scroll-x { overflow-x: scroll;overflow-y: hidden; }
+[data-route="point-of-sale"] .scroll-y { overflow-y: scroll;overflow-x: hidden; }
+[data-route="point-of-sale"] .overflow-hidden { overflow: hidden; }
+[data-route="point-of-sale"] .whitespace-nowrap { white-space: nowrap; }
+[data-route="point-of-sale"] .sticky { position: sticky; top: -1px; }
+[data-route="point-of-sale"] .bg-white { background-color: #fff; }
+[data-route="point-of-sale"] .bg-selected { background-color: #fffdf4; }
+[data-route="point-of-sale"] .border-dashed { border-width:1px; border-style: dashed; }
+[data-route="point-of-sale"] .z-100 { z-index: 100; }
+
+[data-route="point-of-sale"] .frappe-control { margin: 0 !important; width: 100%; }
+[data-route="point-of-sale"] .form-control { font-size: 12px; }
+[data-route="point-of-sale"] .form-group { margin: 0 !important; }
+[data-route="point-of-sale"] .pointer { cursor: pointer; }
+[data-route="point-of-sale"] .no-select { user-select: none; }
+[data-route="point-of-sale"] .item-wrapper:hover { transform: scale(1.02, 1.02); }
+[data-route="point-of-sale"] .hover-underline:hover { text-decoration: underline; }
+[data-route="point-of-sale"] .item-wrapper { transition: scale 0.2s ease-in-out; }
+[data-route="point-of-sale"] .cart-items-section .cart-item-wrapper:not(:first-child) { border-top: none; }
+[data-route="point-of-sale"] .customer-transactions .invoice-wrapper:not(:first-child) { border-top: none; }
+
+[data-route="point-of-sale"] .payment-summary-wrapper:last-child { border-bottom: none; }
+[data-route="point-of-sale"] .item-summary-wrapper:last-child { border-bottom: none; }
+[data-route="point-of-sale"] .total-summary-wrapper:last-child { border-bottom: none; }
+[data-route="point-of-sale"] .invoices-container .invoice-wrapper:last-child { border-bottom: none; }
+[data-route="point-of-sale"] .summary-btns:last-child { margin-right: 0px; }
+[data-route="point-of-sale"] ::-webkit-scrollbar { width: 1px }
+
+[data-route="point-of-sale"] .indicator.grey::before { background-color: #8d99a6; }
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index b72ceb2..405a33c 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -34,12 +34,12 @@
this.calculate_discount_amount();
// Advance calculation applicable to Sales /Purchase Invoice
- if(in_list(["Sales Invoice", "Purchase Invoice"], this.frm.doc.doctype)
+ if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
&& this.frm.doc.docstatus < 2 && !this.frm.doc.is_return) {
this.calculate_total_advance(update_paid_amount);
}
- if (this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.is_pos &&
+ if (in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_pos &&
this.frm.doc.is_return) {
this.update_paid_amount_for_return();
}
@@ -425,7 +425,7 @@
? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.rounding_adjustment)
: this.frm.doc.net_total);
- if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
+ if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
this.frm.doc.base_grand_total = (this.frm.doc.total_taxes_and_charges) ?
flt(this.frm.doc.grand_total * this.frm.doc.conversion_rate) : this.frm.doc.base_net_total;
} else {
@@ -604,7 +604,7 @@
// NOTE:
// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
// total_advance is only for non POS Invoice
- if(this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.is_return){
+ if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_return){
this.calculate_paid_amount();
}
@@ -612,7 +612,7 @@
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);
- if(in_list(["Sales Invoice", "Purchase Invoice"], this.frm.doc.doctype)) {
+ if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)) {
var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
if(this.frm.doc.party_account_currency == this.frm.doc.currency) {
@@ -634,7 +634,7 @@
this.frm.refresh_field("base_paid_amount");
}
- if(this.frm.doc.doctype == "Sales Invoice") {
+ if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
let total_amount_for_payment = (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount)
? flt(total_amount_to_pay - this.frm.doc.loyalty_amount, precision("base_grand_total"))
: total_amount_to_pay;
@@ -691,11 +691,13 @@
if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) {
$.each(this.frm.doc['payments'] || [], function(index, data) {
if(data.default && payment_status && total_amount_to_pay > 0) {
- data.base_amount = flt(total_amount_to_pay, precision("base_amount"));
- data.amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount"));
+ let base_amount = flt(total_amount_to_pay, precision("base_amount", data));
+ frappe.model.set_value(data.doctype, data.name, "base_amount", base_amount);
+ let amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data));
+ frappe.model.set_value(data.doctype, data.name, "amount", amount);
payment_status = false;
} else if(me.frm.doc.paid_amount) {
- data.amount = 0.0;
+ frappe.model.set_value(data.doctype, data.name, "amount", 0.0);
}
});
}
@@ -707,7 +709,7 @@
var base_paid_amount = 0.0;
if(this.frm.doc.is_pos) {
$.each(this.frm.doc['payments'] || [], function(index, data){
- data.base_amount = flt(data.amount * me.frm.doc.conversion_rate, precision("base_amount"));
+ data.base_amount = flt(data.amount * me.frm.doc.conversion_rate, precision("base_amount", data));
paid_amount += data.amount;
base_paid_amount += data.base_amount;
});
@@ -719,14 +721,14 @@
paid_amount += flt(this.frm.doc.loyalty_amount / me.frm.doc.conversion_rate, precision("paid_amount"));
}
- this.frm.doc.paid_amount = flt(paid_amount, precision("paid_amount"));
- this.frm.doc.base_paid_amount = flt(base_paid_amount, precision("base_paid_amount"));
+ this.frm.set_value('paid_amount', flt(paid_amount, precision("paid_amount")));
+ this.frm.set_value('base_paid_amount', flt(base_paid_amount, precision("base_paid_amount")));
},
calculate_change_amount: function(){
this.frm.doc.change_amount = 0.0;
this.frm.doc.base_change_amount = 0.0;
- if(this.frm.doc.doctype == "Sales Invoice"
+ if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
&& this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) {
var payment_types = $.map(this.frm.doc.payments, function(d) { return d.type; });
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index ca897dd..4e50f3d 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -159,6 +159,26 @@
};
});
}
+ if (this.frm.fields_dict["items"].grid.get_field("cost_center")) {
+ this.frm.set_query("cost_center", "items", function(doc) {
+ return {
+ filters: {
+ "company": doc.company,
+ "is_group": 0
+ }
+ };
+ });
+ }
+
+ if (this.frm.fields_dict["items"].grid.get_field("expense_account")) {
+ this.frm.set_query("expense_account", "items", function(doc) {
+ return {
+ filters: {
+ "company": doc.company
+ }
+ };
+ });
+ }
if(frappe.meta.get_docfield(this.frm.doc.doctype, "pricing_rules")) {
this.frm.set_indicator_formatter('pricing_rule', function(doc) {
@@ -631,7 +651,7 @@
let child = frappe.model.add_child(me.frm.doc, "taxes");
child.charge_type = "On Net Total";
child.account_head = tax;
- child.rate = 0;
+ child.rate = rate;
}
});
}
@@ -1815,7 +1835,8 @@
if (doc.tax_category)
filters['tax_category'] = doc.tax_category;
-
+ if (doc.company)
+ filters['company'] = doc.company;
return {
query: "erpnext.controllers.queries.get_tax_template",
filters: filters
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index d89d471..459c01b 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -3,7 +3,7 @@
erpnext.financial_statements = {
"filters": get_filters(),
"formatter": function(value, row, column, data, default_formatter) {
- if (column.fieldname=="account") {
+ if (data && column.fieldname=="account") {
value = data.account_name || value;
column.link_onclick =
@@ -13,7 +13,7 @@
value = default_formatter(value, row, column, data);
- if (!data.parent_account) {
+ if (data && !data.parent_account) {
value = $(`<span>${value}</span>`);
var $value = $(value).css("font-weight", "bold");
diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js
index 17b726e..66ff464 100644
--- a/erpnext/public/js/help_links.js
+++ b/erpnext/public/js/help_links.js
@@ -450,7 +450,7 @@
]
frappe.help.help_links['Form/Address'] = [
- { label: 'Address', url: docsUrl + 'user/manual/en/CRM/contact' },
+ { label: 'Address', url: docsUrl + 'user/manual/en/CRM/address' },
]
frappe.help.help_links['Form/Contact'] = [
diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js
index 44a8cd0..6a923ae 100644
--- a/erpnext/public/js/shopping_cart.js
+++ b/erpnext/public/js/shopping_cart.js
@@ -55,6 +55,7 @@
shopping_cart.show_shoppingcart_dropdown();
shopping_cart.set_cart_count();
shopping_cart.bind_dropdown_cart_buttons();
+ shopping_cart.show_cart_navbar();
});
$.extend(shopping_cart, {
@@ -177,4 +178,12 @@
},
+ show_cart_navbar: function () {
+ frappe.call({
+ method: "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.is_cart_enabled",
+ callback: function(r) {
+ $(".shopping-cart").toggleClass('hidden', r.message ? false : true);
+ }
+ });
+ }
});
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index 99c1b8a..0653267 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -4,7 +4,7 @@
frappe.provide("erpnext.utils");
erpnext.utils.get_party_details = function(frm, method, args, callback) {
- if(!method) {
+ if (!method) {
method = "erpnext.accounts.party.get_party_details";
}
@@ -22,12 +22,12 @@
}
}
- if(!args) {
- if((frm.doctype != "Purchase Order" && frm.doc.customer)
+ if (!args) {
+ if ((frm.doctype != "Purchase Order" && frm.doc.customer)
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
let party_type = "Customer";
- if(frm.doc.quotation_to && frm.doc.quotation_to === "Lead") {
+ if (frm.doc.quotation_to && frm.doc.quotation_to === "Lead") {
party_type = "Lead";
}
@@ -36,7 +36,7 @@
party_type: party_type,
price_list: frm.doc.selling_price_list
};
- } else if(frm.doc.supplier) {
+ } else if (frm.doc.supplier) {
args = {
party: frm.doc.supplier,
party_type: "Supplier",
@@ -78,13 +78,17 @@
args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
}
}
- if(!args || !args.party) return;
+ if (!args || !args.party) return;
- if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
- if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date",
+ if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
+ if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date",
args.posting_date, args.party_type=="Customer" ? "customer": "supplier")) return;
}
+ if (!erpnext.utils.validate_mandatory(frm, "Company", frm.doc.company, args.party_type=="Customer" ? "customer": "supplier")) {
+ return;
+ }
+
args.currency = frm.doc.currency;
args.company = frm.doc.company;
args.doctype = frm.doc.doctype;
@@ -92,14 +96,14 @@
method: method,
args: args,
callback: function(r) {
- if(r.message) {
+ if (r.message) {
frm.supplier_tds = r.message.supplier_tds;
frm.updating_party_details = true;
frappe.run_serially([
() => frm.set_value(r.message),
() => {
frm.updating_party_details = false;
- if(callback) callback();
+ if (callback) callback();
frm.refresh();
erpnext.utils.add_item(frm);
}
@@ -110,9 +114,9 @@
}
erpnext.utils.add_item = function(frm) {
- if(frm.is_new()) {
+ if (frm.is_new()) {
var prev_route = frappe.get_prev_route();
- if(prev_route[1]==='Item' && !(frm.doc.items && frm.doc.items.length)) {
+ if (prev_route[1]==='Item' && !(frm.doc.items && frm.doc.items.length)) {
// add row
var item = frm.add_child('items');
frm.refresh_field('items');
@@ -124,23 +128,23 @@
}
erpnext.utils.get_address_display = function(frm, address_field, display_field, is_your_company_address) {
- if(frm.updating_party_details) return;
+ if (frm.updating_party_details) return;
- if(!address_field) {
- if(frm.doctype != "Purchase Order" && frm.doc.customer) {
+ if (!address_field) {
+ if (frm.doctype != "Purchase Order" && frm.doc.customer) {
address_field = "customer_address";
- } else if(frm.doc.supplier) {
+ } else if (frm.doc.supplier) {
address_field = "supplier_address";
} else return;
}
- if(!display_field) display_field = "address_display";
- if(frm.doc[address_field]) {
+ if (!display_field) display_field = "address_display";
+ if (frm.doc[address_field]) {
frappe.call({
method: "frappe.contacts.doctype.address.address.get_address_display",
args: {"address_dict": frm.doc[address_field] },
callback: function(r) {
- if(r.message) {
+ if (r.message) {
frm.set_value(display_field, r.message)
}
}
@@ -151,15 +155,15 @@
};
erpnext.utils.set_taxes_from_address = function(frm, triggered_from_field, billing_address_field, shipping_address_field) {
- if(frm.updating_party_details) return;
+ if (frm.updating_party_details) return;
- if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
- if(!erpnext.utils.validate_mandatory(frm, "Lead/Customer/Supplier",
+ if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
+ if (!erpnext.utils.validate_mandatory(frm, "Lead / Customer / Supplier",
frm.doc.customer || frm.doc.supplier || frm.doc.lead || frm.doc.party_name, triggered_from_field)) {
return;
}
- if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date",
+ if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date",
frm.doc.posting_date || frm.doc.transaction_date, triggered_from_field)) {
return;
}
@@ -175,8 +179,8 @@
"shipping_address": frm.doc[shipping_address_field]
},
callback: function(r) {
- if(!r.exc){
- if(frm.doc.tax_category != r.message) {
+ if (!r.exc){
+ if (frm.doc.tax_category != r.message) {
frm.set_value("tax_category", r.message);
} else {
erpnext.utils.set_taxes(frm, triggered_from_field);
@@ -187,13 +191,17 @@
};
erpnext.utils.set_taxes = function(frm, triggered_from_field) {
- if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
- if(!erpnext.utils.validate_mandatory(frm, "Lead/Customer/Supplier",
+ if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
+ if (!erpnext.utils.validate_mandatory(frm, "Company", frm.doc.company, triggered_from_field)) {
+ return;
+ }
+
+ if (!erpnext.utils.validate_mandatory(frm, "Lead / Customer / Supplier",
frm.doc.customer || frm.doc.supplier || frm.doc.lead || frm.doc.party_name, triggered_from_field)) {
return;
}
- if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date",
+ if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date",
frm.doc.posting_date || frm.doc.transaction_date, triggered_from_field)) {
return;
}
@@ -230,7 +238,7 @@
"shipping_address": frm.doc.shipping_address_name
},
callback: function(r) {
- if(r.message){
+ if (r.message){
frm.set_value("taxes_and_charges", r.message)
}
}
@@ -238,14 +246,14 @@
};
erpnext.utils.get_contact_details = function(frm) {
- if(frm.updating_party_details) return;
+ if (frm.updating_party_details) return;
- if(frm.doc["contact_person"]) {
+ if (frm.doc["contact_person"]) {
frappe.call({
method: "frappe.contacts.doctype.contact.contact.get_contact_details",
args: {contact: frm.doc.contact_person },
callback: function(r) {
- if(r.message)
+ if (r.message)
frm.set_value(r.message);
}
})
@@ -253,10 +261,10 @@
}
erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) {
- if(!value) {
+ if (!value) {
frm.doc[trigger_on] = "";
refresh_field(trigger_on);
- frappe.msgprint(__("Please enter {0} first", [label]));
+ frappe.throw({message:__("Please enter {0} first", [label]), title:__("Mandatory")});
return false;
}
return true;
@@ -271,12 +279,12 @@
address: frm.doc.shipping_address
},
callback: function(r){
- if(r.message){
+ if (r.message){
frm.set_value("shipping_address", r.message[0]) //Address title or name
frm.set_value("shipping_address_display", r.message[1]) //Address to be displayed on the page
}
- if(callback){
+ if (callback){
return callback();
}
}
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index d75633e..d9f6e1d 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -43,6 +43,7 @@
label: __(me.warehouse_details.type),
default: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
onchange: function(e) {
+ me.warehouse_details.name = this.get_value();
if(me.has_batch && !me.has_serial_no) {
fields = fields.concat(me.get_batch_fields());
@@ -50,7 +51,6 @@
fields = fields.concat(me.get_serial_no_fields());
}
- me.warehouse_details.name = this.get_value();
var batches = this.layout.fields_dict.batches;
if(batches) {
batches.grid.df.data = [];
@@ -98,8 +98,13 @@
numbers.then((data) => {
let auto_fetched_serial_numbers = data.message;
let records_length = auto_fetched_serial_numbers.length;
+ if (!records_length) {
+ const warehouse = me.dialog.fields_dict.warehouse.get_value().bold();
+ frappe.msgprint(__(`Serial numbers unavailable for Item ${me.item.item_code.bold()}
+ under warehouse ${warehouse}. Please try changing warehouse.`));
+ }
if (records_length < qty) {
- frappe.msgprint(`Fetched only ${records_length} serial numbers.`);
+ frappe.msgprint(__(`Fetched only ${records_length} available serial numbers.`));
}
let serial_no_list_field = this.dialog.fields_dict.serial_no;
numbers = auto_fetched_serial_numbers.join('\n');
@@ -333,8 +338,8 @@
};
},
change: function () {
- let val = this.get_value();
- if (val.length === 0) {
+ const batch_no = this.get_value();
+ if (!batch_no) {
this.grid_row.on_grid_fields_dict
.available_qty.set_value(0);
return;
@@ -354,14 +359,11 @@
return;
}
- let batch_number = me.item.batch_no ||
- this.grid_row.on_grid_fields_dict.batch_no.get_value();
-
if (me.warehouse_details.name) {
frappe.call({
method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
args: {
- batch_no: batch_number,
+ batch_no,
warehouse: me.warehouse_details.name,
item_code: me.item_code
},
@@ -445,6 +447,28 @@
serial_no_filters['warehouse'] = me.warehouse_details.name;
}
+ if (me.frm.doc.doctype === 'POS Invoice' && !this.showing_reserved_serial_nos_error) {
+ frappe.call({
+ method: "erpnext.stock.doctype.serial_no.serial_no.get_pos_reserved_serial_nos",
+ args: {
+ item_code: me.item_code,
+ warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : ''
+ }
+ }).then((data) => {
+ if (!data.message[1].length) {
+ this.showing_reserved_serial_nos_error = true;
+ const warehouse = me.dialog.fields_dict.warehouse.get_value().bold();
+ const d = frappe.msgprint(__(`Serial numbers unavailable for Item ${me.item.item_code.bold()}
+ under warehouse ${warehouse}. Please try changing warehouse.`));
+ d.get_close_btn().on('click', () => {
+ this.showing_reserved_serial_nos_error = false;
+ d.hide();
+ });
+ }
+ serial_no_filters['name'] = ["not in", data.message[0]]
+ })
+ }
+
return [
{fieldtype: 'Section Break', label: __('Serial Numbers')},
{
diff --git a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.js b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.js
index 63747af..dac6ac4 100644
--- a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.js
+++ b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.js
@@ -5,21 +5,28 @@
refresh: function(frm) {
frm.set_value("date", frappe.datetime.get_today());
},
- template: function(frm){
- frappe.call({
- "method": "frappe.client.get",
- args: {
- doctype: "Quality Feedback Template",
- name: frm.doc.template
- },
- callback: function(data){
- frm.fields_dict.parameters.grid.remove_all();
- for (var i in data.message.parameters){
- frm.add_child("parameters");
- frm.fields_dict.parameters.get_value()[i].parameter = data.message.parameters[i].parameter;
+
+ template: function(frm) {
+ if (frm.doc.template) {
+ frappe.call({
+ "method": "frappe.client.get",
+ args: {
+ doctype: "Quality Feedback Template",
+ name: frm.doc.template
+ },
+ callback: function(data) {
+ if (data && data.message) {
+ frm.fields_dict.parameters.grid.remove_all();
+
+ // fetch parameters from template and autofill
+ for (let template_parameter of data.message.parameters) {
+ let row = frm.add_child("parameters");
+ row.parameter = template_parameter.parameter;
+ }
+ frm.refresh();
+ }
}
- frm.refresh();
- }
- });
+ });
+ }
}
});
diff --git a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.json b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.json
index 460438a..ab9084f 100644
--- a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.json
+++ b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "format:FDBK-{#####}",
"creation": "2019-05-26 21:23:05.308379",
"doctype": "DocType",
@@ -53,12 +54,13 @@
{
"fieldname": "document_name",
"fieldtype": "Dynamic Link",
- "label": "Name",
+ "label": "Feedback By",
"options": "document_type",
"reqd": 1
}
],
- "modified": "2019-05-28 15:16:01.161662",
+ "links": [],
+ "modified": "2020-07-03 15:50:58.589302",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Feedback",
diff --git a/erpnext/quality_management/doctype/quality_feedback_template/quality_feedback_template.json b/erpnext/quality_management/doctype/quality_feedback_template/quality_feedback_template.json
index 31efd04..bdc9dba 100644
--- a/erpnext/quality_management/doctype/quality_feedback_template/quality_feedback_template.json
+++ b/erpnext/quality_management/doctype/quality_feedback_template/quality_feedback_template.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "format:TMPL-{template}",
"creation": "2019-05-26 21:17:24.283061",
"doctype": "DocType",
@@ -30,10 +31,12 @@
"fieldname": "parameters",
"fieldtype": "Table",
"label": "Parameters",
- "options": "Quality Feedback Template Parameter"
+ "options": "Quality Feedback Template Parameter",
+ "reqd": 1
}
],
- "modified": "2019-05-26 21:48:47.770610",
+ "links": [],
+ "modified": "2020-07-03 16:06:03.749415",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Feedback Template",
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js
index a1cea8f..c744266 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js
@@ -3,6 +3,7 @@
frappe.ui.form.on('GSTR 3B Report', {
refresh : function(frm) {
+ frm.doc.__unsaved = 1;
if(!frm.is_new()) {
frm.set_intro(__("Please save the report again to rebuild or update"));
frm.add_custom_button(__('Download JSON'), function() {
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index 619734f..2d306ba 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -243,20 +243,15 @@
osup_det = self.report_dict["sup_details"]["osup_det"]
- for d in inter_state_supply.get("Unregistered", []):
- self.report_dict["inter_sup"]["unreg_details"].append(d)
- osup_det["txval"] = flt(osup_det["txval"] + d["txval"], 2)
- osup_det["iamt"] = flt(osup_det["iamt"] + d["iamt"], 2)
+ for key, value in iteritems(inter_state_supply):
+ if key[0] == "Unregistered":
+ self.report_dict["inter_sup"]["unreg_details"].append(value)
- for d in inter_state_supply.get("Registered Composition", []):
- self.report_dict["inter_sup"]["comp_details"].append(d)
- osup_det["txval"] = flt(osup_det["txval"] + d["txval"], 2)
- osup_det["iamt"] = flt(osup_det["iamt"] + d["iamt"], 2)
+ if key[0] == "Registered Composition":
+ self.report_dict["inter_sup"]["comp_details"].append(value)
- for d in inter_state_supply.get("UIN Holders", []):
- self.report_dict["inter_sup"]["uin_details"].append(d)
- osup_det["txval"] = flt(osup_det["txval"] + d["txval"], 2)
- osup_det["iamt"] = flt(osup_det["iamt"] + d["iamt"], 2)
+ if key[0] == "UIN Holders":
+ self.report_dict["inter_sup"]["uin_details"].append(value)
def get_total_taxable_value(self, doctype, reverse_charge):
@@ -301,41 +296,55 @@
(self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)[0].total
def get_inter_state_supplies(self, state_number):
-
- inter_state_supply_taxable_value = frappe.db.sql(""" select sum(s.net_total) as total, s.place_of_supply, s.gst_category
- from `tabSales Invoice` s where s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s
- and s.company = %s and s.company_gstin = %s and s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders')
- group by s.gst_category, s.place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
-
- inter_state_supply_tax = frappe.db.sql(""" select sum(t.tax_amount_after_discount_amount) as tax_amount, s.place_of_supply, s.gst_category
- from `tabSales Invoice` s, `tabSales Taxes and Charges` t
+ inter_state_supply_tax = frappe.db.sql(""" select t.account_head, t.tax_amount_after_discount_amount as tax_amount,
+ s.name, s.net_total, s.place_of_supply, s.gst_category from `tabSales Invoice` s, `tabSales Taxes and Charges` t
where t.parent = s.name and s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s
and s.company = %s and s.company_gstin = %s and s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders')
- group by s.gst_category, s.place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+ """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
- inter_state_supply_tax_mapping={}
+ inter_state_supply_tax_mapping = {}
inter_state_supply_details = {}
for d in inter_state_supply_tax:
- inter_state_supply_tax_mapping.setdefault(d.place_of_supply, d.tax_amount)
+ inter_state_supply_tax_mapping.setdefault(d.name, {
+ 'place_of_supply': d.place_of_supply,
+ 'taxable_value': d.net_total,
+ 'camt': 0.0,
+ 'samt': 0.0,
+ 'iamt': 0.0,
+ 'csamt': 0.0
+ })
- for d in inter_state_supply_taxable_value:
- inter_state_supply_details.setdefault(
- d.gst_category, []
- )
+ if d.account_head in [d.cgst_account for d in self.account_heads]:
+ inter_state_supply_tax_mapping[d.name]['camt'] += d.tax_amount
+ if d.account_head in [d.sgst_account for d in self.account_heads]:
+ inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount
+
+ if d.account_head in [d.igst_account for d in self.account_heads]:
+ inter_state_supply_tax_mapping[d.name]['iamt'] += d.tax_amount
+
+ if d.account_head in [d.cess_account for d in self.account_heads]:
+ inter_state_supply_tax_mapping[d.name]['csamt'] += d.tax_amount
+
+ for key, value in iteritems(inter_state_supply_tax_mapping):
if d.place_of_supply:
+ osup_det = self.report_dict["sup_details"]["osup_det"]
+ osup_det["txval"] = flt(osup_det["txval"] + value['taxable_value'], 2)
+ osup_det["iamt"] = flt(osup_det["iamt"] + value['iamt'], 2)
+ osup_det["camt"] = flt(osup_det["camt"] + value['camt'], 2)
+ osup_det["samt"] = flt(osup_det["samt"] + value['samt'], 2)
+ osup_det["csamt"] = flt(osup_det["csamt"] + value['csamt'], 2)
+
if state_number != d.place_of_supply.split("-")[0]:
- inter_state_supply_details[d.gst_category].append({
+ inter_state_supply_details.setdefault((d.gst_category, d.place_of_supply), {
+ "txval": 0.0,
"pos": d.place_of_supply.split("-")[0],
- "txval": flt(d.total, 2),
- "iamt": flt(inter_state_supply_tax_mapping.get(d.place_of_supply), 2)
+ "iamt": 0.0
})
- else:
- osup_det = self.report_dict["sup_details"]["osup_det"]
- osup_det["txval"] = flt(osup_det["txval"] + d.total, 2)
- osup_det["camt"] = flt(osup_det["camt"] + inter_state_supply_tax_mapping.get(d.place_of_supply)/2, 2)
- osup_det["samt"] = flt(osup_det["samt"] + inter_state_supply_tax_mapping.get(d.place_of_supply)/2, 2)
+
+ inter_state_supply_details[(d.gst_category, d.place_of_supply)]['txval'] += value['taxable_value']
+ inter_state_supply_details[(d.gst_category, d.place_of_supply)]['iamt'] += value['iamt']
return inter_state_supply_details
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 05ffa87..fe7e0c8 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
import frappe, re, json
from frappe import _
-from frappe.utils import cstr, flt, date_diff, nowdate
+from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words
from erpnext.regional.india import states, state_numbers
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
from erpnext.controllers.accounts_controller import get_taxes_and_charges
@@ -458,19 +458,23 @@
@frappe.whitelist()
def download_ewb_json():
- data = frappe._dict(frappe.local.form_dict)
-
- frappe.local.response.filecontent = json.dumps(data['data'], indent=4, sort_keys=True)
+ data = json.loads(frappe.local.form_dict.data)
+ frappe.local.response.filecontent = json.dumps(data, indent=4, sort_keys=True)
frappe.local.response.type = 'download'
- billList = json.loads(data['data'])['billLists']
+ filename_prefix = 'Bulk'
+ docname = frappe.local.form_dict.docname
+ if docname:
+ if docname.startswith('['):
+ docname = json.loads(docname)
+ if len(docname) == 1:
+ docname = docname[0]
- if len(billList) > 1:
- doc_name = 'Bulk'
- else:
- doc_name = data['docname']
+ if not isinstance(docname, list):
+ # removes characters not allowed in a filename (https://stackoverflow.com/a/38766141/4767738)
+ filename_prefix = re.sub('[^\w_.)( -]', '', docname)
- frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(doc_name, frappe.utils.random_string(5))
+ frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(filename_prefix, frappe.utils.random_string(5))
@frappe.whitelist()
def get_gstins_for_company(company):
@@ -644,6 +648,7 @@
else:
return int(state_code)
+@frappe.whitelist()
def get_gst_accounts(company, account_wise=False):
gst_accounts = frappe._dict()
gst_settings_accounts = frappe.get_all("GST Account",
@@ -662,14 +667,55 @@
return gst_accounts
-def make_reverse_charge_entries(doc, method):
+def update_grand_total_for_rcm(doc, method):
country = frappe.get_cached_value('Company', doc.company, 'country')
if country != 'India':
return
if doc.reverse_charge == 'Y':
- gl_entries = []
+ gst_accounts = get_gst_accounts(doc.company)
+ gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
+ + gst_accounts.get('igst_account')
+
+ gst_tax = 0
+ for tax in doc.get('taxes'):
+ if tax.category not in ("Total", "Valuation and Total"):
+ continue
+
+ if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
+ gst_tax += tax.base_tax_amount_after_discount_amount
+
+ doc.taxes_and_charges_added -= gst_tax
+ doc.total_taxes_and_charges -= gst_tax
+
+ update_totals(gst_tax, doc)
+
+def update_totals(gst_tax, doc):
+ doc.grand_total -= gst_tax
+
+ if doc.meta.get_field("rounded_total"):
+ if doc.is_rounded_total_disabled():
+ doc.outstanding_amount = doc.grand_total
+ else:
+ doc.rounded_total = round_based_on_smallest_currency_fraction(doc.grand_total,
+ doc.currency, doc.precision("rounded_total"))
+
+ doc.rounding_adjustment += flt(doc.rounded_total - doc.grand_total,
+ doc.precision("rounding_adjustment"))
+
+ doc.outstanding_amount = doc.rounded_total or doc.grand_total
+
+ doc.in_words = money_in_words(doc.grand_total, doc.currency)
+ doc.set_payment_schedule()
+
+def make_regional_gl_entries(gl_entries, doc):
+ country = frappe.get_cached_value('Company', doc.company, 'country')
+
+ if country != 'India':
+ return
+
+ if doc.reverse_charge == 'Y':
gst_accounts = get_gst_accounts(doc.company)
gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
+ gst_accounts.get('igst_account')
@@ -694,19 +740,4 @@
}, account_currency, item=tax)
)
- gl_entries.append(doc.get_gl_dict(
- {
- "account": doc.credit_to if doc.doctype == 'Purchase Invoice' else doc.debit_to,
- "cost_center": doc.cost_center,
- "posting_date": doc.posting_date,
- "party_type": 'Supplier',
- "party": doc.supplier,
- "against": tax.account_head,
- "debit": tax.base_tax_amount_after_discount_amount,
- "debit_in_account_currency": tax.base_tax_amount_after_discount_amount \
- if account_currency==doc.company_currency \
- else tax.tax_amount_after_discount_amount
- }, account_currency, item=doc)
- )
-
- make_gl_entries(gl_entries)
\ No newline at end of file
+ return gl_entries
\ No newline at end of file
diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js
index d8638ab..55f12cf 100644
--- a/erpnext/regional/report/datev/datev.js
+++ b/erpnext/regional/report/datev/datev.js
@@ -30,7 +30,7 @@
}
],
onload: function(query_report) {
- query_report.page.add_inner_button("Download DATEV Export", () => {
+ query_report.page.add_menu_item(__("Download DATEV File"), () => {
const filters = JSON.stringify(query_report.get_values());
window.open(`/api/method/erpnext.regional.report.datev.datev.download_datev_csv?filters=${filters}`);
});
diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py
index 3cc65fe..eed62a8 100644
--- a/erpnext/regional/report/datev/test_datev.py
+++ b/erpnext/regional/report/datev/test_datev.py
@@ -90,7 +90,7 @@
if not frappe.db.exists("Customer", customer_name):
customer = frappe.get_doc({
- "doctype": "Customer",
+ "doctype": "Customer",
"customer_name": customer_name,
"customer_type": "Company",
"accounts": [{
@@ -155,17 +155,17 @@
setup_fiscal_year()
warehouse = frappe.db.get_value("Item Default", {
- "parent": item.name,
+ "parent": item.name,
"company": self.company.name
}, "default_warehouse")
income_account = frappe.db.get_value("Account", {
- "account_number": "4200",
+ "account_number": "4200",
"company": self.company.name
}, "name")
tax_account = frappe.db.get_value("Account", {
- "account_number": "3806",
+ "account_number": "3806",
"company": self.company.name
}, "name")
@@ -186,9 +186,12 @@
"charge_type": "On Net Total",
"account_head": tax_account,
"description": "Umsatzsteuer 19 %",
- "rate": 19
+ "rate": 19,
+ "cost_center": self.company.cost_center
})
+ si.cost_center = self.company.cost_center
+
si.save()
si.submit()
@@ -196,7 +199,7 @@
def is_subset(get_data, allowed_keys):
"""
Validate that the dict contains only allowed keys.
-
+
Params:
get_data -- Function that returns a list of dicts.
allowed_keys -- List of allowed keys
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 43b1ea8..8885b88 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -118,7 +118,7 @@
row.append(invoice_details.get(fieldname))
taxable_value = 0
- if invoice in self.cgst_igst_invoices:
+ if invoice in self.cgst_sgst_invoices:
division_factor = 2
else:
division_factor = 1
@@ -129,6 +129,8 @@
taxable_value += abs(net_amount)
elif not self.item_tax_rate.get(invoice):
taxable_value += abs(net_amount)
+ elif tax_rate:
+ taxable_value += abs(net_amount)
row += [tax_rate or 0, taxable_value]
@@ -227,7 +229,7 @@
self.items_based_on_tax_rate = {}
self.invoice_cess = frappe._dict()
- self.cgst_igst_invoices = []
+ self.cgst_sgst_invoices = []
unidentified_gst_accounts = []
for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
@@ -251,8 +253,8 @@
tax_rate = tax_amounts[0]
if cgst_or_sgst:
tax_rate *= 2
- if parent not in self.cgst_igst_invoices:
- self.cgst_igst_invoices.append(parent)
+ if parent not in self.cgst_sgst_invoices:
+ self.cgst_sgst_invoices.append(parent)
rate_based_dict = self.items_based_on_tax_rate\
.setdefault(parent, {}).setdefault(tax_rate, [])
diff --git a/erpnext/regional/report/gstr_2/gstr_2.py b/erpnext/regional/report/gstr_2/gstr_2.py
index f326fe0..f899349 100644
--- a/erpnext/regional/report/gstr_2/gstr_2.py
+++ b/erpnext/regional/report/gstr_2/gstr_2.py
@@ -44,30 +44,30 @@
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
invoice_details = self.invoices.get(inv)
for rate, items in items_based_on_rate.items():
- if inv not in self.igst_invoices:
- rate = rate / 2
- row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
- tax_amount = taxable_value * rate / 100
- row += [0, tax_amount, tax_amount]
- else:
- row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
- tax_amount = taxable_value * rate / 100
- row += [tax_amount, 0, 0]
+ if rate:
+ if inv not in self.igst_invoices:
+ rate = rate / 2
+ row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
+ tax_amount = taxable_value * rate / 100
+ row += [0, tax_amount, tax_amount]
+ else:
+ row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
+ tax_amount = taxable_value * rate / 100
+ row += [tax_amount, 0, 0]
+ row += [
+ self.invoice_cess.get(inv),
+ invoice_details.get('eligibility_for_itc'),
+ invoice_details.get('itc_integrated_tax'),
+ invoice_details.get('itc_central_tax'),
+ invoice_details.get('itc_state_tax'),
+ invoice_details.get('itc_cess_amount')
+ ]
+ if self.filters.get("type_of_business") == "CDNR":
+ row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
+ row.append("C" if invoice_details.return_against else "R")
- row += [
- self.invoice_cess.get(inv),
- invoice_details.get('eligibility_for_itc'),
- invoice_details.get('itc_integrated_tax'),
- invoice_details.get('itc_central_tax'),
- invoice_details.get('itc_state_tax'),
- invoice_details.get('itc_cess_amount')
- ]
- if self.filters.get("type_of_business") == "CDNR":
- row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
- row.append("C" if invoice_details.return_against else "R")
-
- self.data.append(row)
+ self.data.append(row)
def get_igst_invoices(self):
self.igst_invoices = []
@@ -86,7 +86,7 @@
conditions += opts[1]
if self.filters.get("type_of_business") == "B2B":
- conditions += "and ifnull(gst_category, '') != 'Overseas' and is_return != 1 "
+ conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1 "
elif self.filters.get("type_of_business") == "CDNR":
conditions += """ and is_return = 1 """
diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py
index 772bbf5..a0425f6 100644
--- a/erpnext/regional/united_arab_emirates/utils.py
+++ b/erpnext/regional/united_arab_emirates/utils.py
@@ -11,14 +11,17 @@
for row in doc.items:
tax_rate = 0.0
- item_tax_rate = frappe.parse_json(row.item_tax_rate)
+ item_tax_rate = 0.0
+
+ if row.item_tax_rate:
+ item_tax_rate = frappe.parse_json(row.item_tax_rate)
# First check if tax rate is present
# If not then look up in item_wise_tax_detail
if item_tax_rate:
for account, rate in iteritems(item_tax_rate):
tax_rate += rate
- elif itemised_tax.get(row.item_code):
+ elif row.item_code and itemised_tax.get(row.item_code):
tax_rate = sum([tax.get('tax_rate', 0) for d, tax in itemised_tax.get(row.item_code).items()])
row.tax_rate = flt(tax_rate, row.precision("tax_rate"))
diff --git a/erpnext/regional/united_states/setup.py b/erpnext/regional/united_states/setup.py
index cae28be..2b0ecaf 100644
--- a/erpnext/regional/united_states/setup.py
+++ b/erpnext/regional/united_states/setup.py
@@ -14,6 +14,22 @@
'Supplier': [
dict(fieldname='irs_1099', fieldtype='Check', insert_after='tax_id',
label='Is IRS 1099 reporting required for supplier?')
+ ],
+ 'Sales Order': [
+ dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges',
+ label='Is customer exempted from sales tax?')
+ ],
+ 'Sales Invoice': [
+ dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_section',
+ label='Is customer exempted from sales tax?')
+ ],
+ 'Customer': [
+ dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='represents_company',
+ label='Is customer exempted from sales tax?')
+ ],
+ 'Quotation': [
+ dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges',
+ label='Is customer exempted from sales tax?')
]
}
create_custom_fields(custom_fields, update=update)
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py
index a748f9a..357deaa 100644
--- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py
+++ b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py
@@ -65,6 +65,7 @@
return invoice.name
+@frappe.whitelist()
def item_query_restaurant(doctype='Item', txt='', searchfield='name', start=0, page_len=20, filters=None, as_dict=False):
'''Return items that are selected in active menu of the restaurant'''
restaurant, menu = get_restaurant_and_menu_name(filters['table'])
@@ -84,4 +85,4 @@
if not menu:
frappe.throw(_('Please set an active menu for Restaurant {0}').format(restaurant))
- return restaurant, menu
\ No newline at end of file
+ return restaurant, menu
diff --git a/erpnext/selling/dashboard_chart/item_wise_annual_sales/item_wise_annual_sales.json b/erpnext/selling/dashboard_chart/item_wise_annual_sales/item_wise_annual_sales.json
new file mode 100644
index 0000000..290e526
--- /dev/null
+++ b/erpnext/selling/dashboard_chart/item_wise_annual_sales/item_wise_annual_sales.json
@@ -0,0 +1,24 @@
+{
+ "chart_name": "Item-wise Annual Sales",
+ "chart_type": "Report",
+ "creation": "2020-07-20 20:17:16.474566",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"to_date\":\"frappe.datetime.nowdate()\"}",
+ "filters_json": "{\"from_date\":\"2020-06-22\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 14:42:25.512675",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Item-wise Annual Sales",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Item-wise Sales History",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/selling/dashboard_chart/sales_order_analysis/sales_order_analysis.json b/erpnext/selling/dashboard_chart/sales_order_analysis/sales_order_analysis.json
new file mode 100644
index 0000000..5e1a0d9
--- /dev/null
+++ b/erpnext/selling/dashboard_chart/sales_order_analysis/sales_order_analysis.json
@@ -0,0 +1,24 @@
+{
+ "chart_name": "Sales Order Analysis",
+ "chart_type": "Report",
+ "creation": "2020-07-20 20:17:16.440393",
+ "custom_options": "{\"type\": \"donut\", \"height\": 300, \"axisOptions\": {\"shortenYAxisNumbers\": 1}}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"to_date\":\"frappe.datetime.nowdate()\"}",
+ "filters_json": "{\"status\":[\"To Bill\",\"To Deliver\"],\"group_by_so\":0,\"from_date\":\"2020-06-22\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 17:06:05.750660",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Sales Order Analysis",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Sales Order Analysis",
+ "timeseries": 0,
+ "type": "Donut",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json b/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json
new file mode 100644
index 0000000..914d915
--- /dev/null
+++ b/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json
@@ -0,0 +1,24 @@
+{
+ "chart_name": "Sales Order Trends",
+ "chart_type": "Report",
+ "creation": "2020-07-20 20:17:16.508240",
+ "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
+ "filters_json": "{\"period\":\"Monthly\",\"based_on\":\"Item\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 16:24:45.726270",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Sales Order Trends",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Sales Order Trends",
+ "timeseries": 0,
+ "type": "Line",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/selling/dashboard_chart/top_customers/top_customers.json b/erpnext/selling/dashboard_chart/top_customers/top_customers.json
new file mode 100644
index 0000000..59a2ba3
--- /dev/null
+++ b/erpnext/selling/dashboard_chart/top_customers/top_customers.json
@@ -0,0 +1,24 @@
+{
+ "chart_name": "Top Customers",
+ "chart_type": "Report",
+ "creation": "2020-07-20 20:17:16.539281",
+ "custom_options": "",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
+ "filters_json": "{\"period\":\"Yearly\",\"based_on\":\"Customer\"}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 17:03:10.320147",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Top Customers",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Delivery Note Trends",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/selling/dashboard_fixtures.py b/erpnext/selling/dashboard_fixtures.py
deleted file mode 100644
index 889cb88..0000000
--- a/erpnext/selling/dashboard_fixtures.py
+++ /dev/null
@@ -1,198 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-import json
-from frappe import _
-from frappe.utils import nowdate
-from erpnext.accounts.utils import get_fiscal_year
-
-def get_data():
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards(),
- })
-
-def get_company_for_dashboards():
- company = frappe.defaults.get_defaults().company
- if company:
- return company
- else:
- company_list = frappe.get_list("Company")
- if company_list:
- return company_list[0].name
- return None
-
-company = frappe.get_doc("Company", get_company_for_dashboards())
-fiscal_year = get_fiscal_year(nowdate(), as_dict=1)
-fiscal_year_name = fiscal_year.get("name")
-start_date = str(fiscal_year.get("year_start_date"))
-end_date = str(fiscal_year.get("year_end_date"))
-
-def get_dashboards():
- return [{
- "name": "Selling",
- "dashboard_name": "Selling",
- "charts": [
- { "chart": "Sales Order Trends", "width": "Full"},
- { "chart": "Top Customers", "width": "Half"},
- { "chart": "Sales Order Analysis", "width": "Half"},
- { "chart": "Item-wise Annual Sales", "width": "Full"}
- ],
- "cards": [
- { "card": "Annual Sales"},
- { "card": "Sales Orders to Deliver"},
- { "card": "Sales Orders to Bill"},
- { "card": "Active Customers"}
- ]
- }]
-
-def get_charts():
- return [
- {
- "name": "Sales Order Analysis",
- "chart_name": _("Sales Order Analysis"),
- "chart_type": "Report",
- "custom_options": json.dumps({
- "type": "donut",
- "height": 300,
- "axisOptions": {"shortenYAxisNumbers": 1}
- }),
- "doctype": "Dashboard Chart",
- "filters_json": json.dumps({
- "company": company.name,
- "from_date": start_date,
- "to_date": end_date
- }),
- "is_custom": 1,
- "is_public": 1,
- "owner": "Administrator",
- "report_name": "Sales Order Analysis",
- "type": "Donut"
- },
- {
- "name": "Item-wise Annual Sales",
- "chart_name": _("Item-wise Annual Sales"),
- "chart_type": "Report",
- "doctype": "Dashboard Chart",
- "filters_json": json.dumps({
- "company": company.name,
- "from_date": start_date,
- "to_date": end_date
- }),
- "is_custom": 1,
- "is_public": 1,
- "owner": "Administrator",
- "report_name": "Item-wise Sales History",
- "type": "Bar"
- },
- {
- "name": "Sales Order Trends",
- "chart_name": _("Sales Order Trends"),
- "chart_type": "Report",
- "custom_options": json.dumps({
- "type": "line",
- "axisOptions": {"shortenYAxisNumbers": 1},
- "tooltipOptions": {},
- "lineOptions": {
- "regionFill": 1
- }
- }),
- "doctype": "Dashboard Chart",
- "filters_json": json.dumps({
- "company": company.name,
- "period": "Monthly",
- "fiscal_year": fiscal_year_name,
- "based_on": "Item"
- }),
- "is_custom": 1,
- "is_public": 1,
- "owner": "Administrator",
- "report_name": "Sales Order Trends",
- "type": "Line"
- },
- {
- "name": "Top Customers",
- "chart_name": _("Top Customers"),
- "chart_type": "Report",
- "doctype": "Dashboard Chart",
- "filters_json": json.dumps({
- "company": company.name,
- "period": "Monthly",
- "fiscal_year": fiscal_year_name,
- "based_on": "Customer"
- }),
- "is_custom": 1,
- "is_public": 1,
- "owner": "Administrator",
- "report_name": "Delivery Note Trends",
- "type": "Bar"
- }
- ]
-
-def get_number_cards():
- return [
- {
- "name": "Annual Sales",
- "aggregate_function_based_on": "base_net_total",
- "doctype": "Number Card",
- "document_type": "Sales Order",
- "filters_json": json.dumps([
- ["Sales Order", "transaction_date", "Between", [start_date, end_date], False],
- ["Sales Order", "status", "not in", ["Draft", "Cancelled", "Closed", None], False],
- ["Sales Order", "docstatus", "=", 1, False],
- ["Sales Order", "company", "=", company.name, False]
- ]),
- "function": "Sum",
- "is_public": 1,
- "label": _("Annual Sales"),
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Monthly"
- },
- {
- "name": "Sales Orders to Deliver",
- "doctype": "Number Card",
- "document_type": "Sales Order",
- "filters_json": json.dumps([
- ["Sales Order", "status", "in", ["To Deliver and Bill", "To Deliver", None], False],
- ["Sales Order", "docstatus", "=", 1, False],
- ["Sales Order", "company", "=", company.name, False]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Sales Orders to Deliver"),
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- },
- {
- "name": "Sales Orders to Bill",
- "doctype": "Number Card",
- "document_type": "Sales Order",
- "filters_json": json.dumps([
- ["Sales Order", "status", "in", ["To Deliver and Bill", "To Bill", None], False],
- ["Sales Order", "docstatus", "=", 1, False],
- ["Sales Order", "company", "=", company.name, False]
- ]),
- "function": "Count",
- "is_public": 1,
- "label": _("Sales Orders to Bill"),
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Weekly"
- },
- {
- "name": "Active Customers",
- "doctype": "Number Card",
- "document_type": "Customer",
- "filters_json": json.dumps([["Customer", "disabled", "=", "0"]]),
- "function": "Count",
- "is_public": 1,
- "label": "Active Customers",
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Monthly"
- }
- ]
\ No newline at end of file
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index d70c64f..ca62488 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -340,13 +340,13 @@
return lp_details
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
from erpnext.controllers.queries import get_fields
+ fields = ["name", "customer_name", "customer_group", "territory"]
if frappe.db.get_default("cust_master_name") == "Customer Name":
fields = ["name", "customer_group", "territory"]
- else:
- fields = ["name", "customer_name", "customer_group", "territory"]
fields = get_fields("Customer", fields)
@@ -542,6 +542,8 @@
return address
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, filters):
customer = filters.get('customer')
return frappe.db.sql("""
@@ -552,4 +554,4 @@
""", {
'customer': customer,
'txt': '%%%s%%' % txt
- })
+ })
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js
deleted file mode 100644
index f24caf7..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.js
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('POS Closing Voucher', {
- onload: function(frm) {
- frm.set_query("pos_profile", function(doc) {
- return {
- filters: {
- 'user': doc.user
- }
- };
- });
-
- frm.set_query("user", function(doc) {
- return {
- query: "erpnext.selling.doctype.pos_closing_voucher.pos_closing_voucher.get_cashiers",
- filters: {
- 'parent': doc.pos_profile
- }
- };
- });
- },
-
- total_amount: function(frm) {
- get_difference_amount(frm);
- },
- custody_amount: function(frm){
- get_difference_amount(frm);
- },
- expense_amount: function(frm){
- get_difference_amount(frm);
- },
- refresh: function(frm) {
- get_closing_voucher_details(frm);
- },
- period_start_date: function(frm) {
- get_closing_voucher_details(frm);
- },
- period_end_date: function(frm) {
- get_closing_voucher_details(frm);
- },
- company: function(frm) {
- get_closing_voucher_details(frm);
- },
- pos_profile: function(frm) {
- get_closing_voucher_details(frm);
- },
- user: function(frm) {
- get_closing_voucher_details(frm);
- },
-});
-
-frappe.ui.form.on('POS Closing Voucher Details', {
- collected_amount: function(doc, cdt, cdn) {
- var row = locals[cdt][cdn];
- frappe.model.set_value(cdt, cdn, "difference", row.collected_amount - row.expected_amount);
- }
-});
-
-var get_difference_amount = function(frm){
- frm.doc.difference = frm.doc.total_amount - frm.doc.custody_amount - frm.doc.expense_amount;
- refresh_field("difference");
-};
-
-var get_closing_voucher_details = function(frm) {
- if (frm.doc.period_end_date && frm.doc.period_start_date && frm.doc.company && frm.doc.pos_profile && frm.doc.user) {
- frappe.call({
- method: "get_closing_voucher_details",
- doc: frm.doc,
- callback: function(r) {
- if (r.message) {
- refresh_field("payment_reconciliation");
- refresh_field("sales_invoices_summary");
- refresh_field("taxes");
-
- refresh_field("grand_total");
- refresh_field("net_total");
- refresh_field("total_quantity");
- refresh_field("total_amount");
-
- frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
- }
- }
- });
- }
-
-};
diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json
deleted file mode 100644
index 2ac5779..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.json
+++ /dev/null
@@ -1,1016 +0,0 @@
-{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "POS-CLO-.YYYY.-.#####",
- "beta": 0,
- "creation": "2018-05-28 19:06:40.830043",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fieldname": "period_start_date",
- "fieldtype": "Date",
- "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": "Period Start Date",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fieldname": "period_end_date",
- "fieldtype": "Date",
- "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": "Period End Date",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "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": "Posting Date",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_5",
- "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
- },
- {
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_7",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "pos_profile",
- "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": "POS Profile",
- "length": 0,
- "no_copy": 0,
- "options": "POS Profile",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "user",
- "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": "Cashier",
- "length": 0,
- "no_copy": 0,
- "options": "User",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expense_details_section",
- "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": "Expense Details",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expense_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": "Expense Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "custody_amount",
- "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": "Amount in Custody",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_13",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "total_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": "Total Collected Amount",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "difference",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Difference",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_9",
- "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": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "payment_reconciliation_details",
- "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,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "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,
- "label": "Modes of Payment",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "payment_reconciliation",
- "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": "Payment Reconciliation",
- "length": 0,
- "no_copy": 0,
- "options": "POS Closing Voucher Details",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "columns": 0,
- "fieldname": "section_break_13",
- "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": "Details",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "grand_total",
- "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": "Grand Total",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "net_total",
- "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": "Net Total",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "total_quantity",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Total Quantity",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_16",
- "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,
- "label": "Taxes",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "taxes",
- "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": "Taxes",
- "length": 0,
- "no_copy": 0,
- "options": "POS Closing Voucher Taxes",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "columns": 0,
- "fieldname": "section_break_12",
- "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": "Linked Invoices",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_invoices_summary",
- "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": "Sales Invoices Summary",
- "length": 0,
- "no_copy": 0,
- "options": "POS Closing Voucher Invoices",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_14",
- "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
- },
- {
- "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": "POS Closing Voucher",
- "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
- }
- ],
- "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": "2019-01-28 12:33:45.217813",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "POS Closing Voucher",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Sales Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "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
-}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py b/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py
deleted file mode 100644
index bb5f83e..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher/pos_closing_voucher.py
+++ /dev/null
@@ -1,188 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from collections import defaultdict
-from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
-import json
-
-class POSClosingVoucher(Document):
- def get_closing_voucher_details(self):
- filters = {
- 'doc': self.name,
- 'from_date': self.period_start_date,
- 'to_date': self.period_end_date,
- 'company': self.company,
- 'pos_profile': self.pos_profile,
- 'user': self.user,
- 'is_pos': 1
- }
-
- invoice_list = get_invoices(filters)
- self.set_invoice_list(invoice_list)
-
- sales_summary = get_sales_summary(invoice_list)
- self.set_sales_summary_values(sales_summary)
- self.total_amount = sales_summary['grand_total']
-
- if not self.get('payment_reconciliation'):
- mop = get_mode_of_payment_details(invoice_list)
- self.set_mode_of_payments(mop)
-
- taxes = get_tax_details(invoice_list)
- self.set_taxes(taxes)
-
- return self.get_payment_reconciliation_details()
-
- def validate(self):
- user = frappe.get_all('POS Closing Voucher',
- filters = {
- 'user': self.user,
- 'docstatus': 1
- },
- or_filters = {
- 'period_start_date': ('between', [self.period_start_date, self.period_end_date]),
- 'period_end_date': ('between', [self.period_start_date, self.period_end_date])
- })
-
- if user:
- frappe.throw(_("POS Closing Voucher alreday exists for {0} between date {1} and {2}")
- .format(self.user, self.period_start_date, self.period_end_date))
-
- def set_invoice_list(self, invoice_list):
- self.sales_invoices_summary = []
- for invoice in invoice_list:
- self.append('sales_invoices_summary', {
- 'invoice': invoice['name'],
- 'qty_of_items': invoice['pos_total_qty'],
- 'grand_total': invoice['grand_total']
- })
-
- def set_sales_summary_values(self, sales_summary):
- self.grand_total = sales_summary['grand_total']
- self.net_total = sales_summary['net_total']
- self.total_quantity = sales_summary['total_qty']
-
- def set_mode_of_payments(self, mop):
- self.payment_reconciliation = []
- for m in mop:
- self.append('payment_reconciliation', {
- 'mode_of_payment': m['name'],
- 'expected_amount': m['amount']
- })
-
- def set_taxes(self, taxes):
- self.taxes = []
- for tax in taxes:
- self.append('taxes', {
- 'rate': tax['rate'],
- 'amount': tax['amount']
- })
-
- def get_payment_reconciliation_details(self):
- currency = get_company_currency(self)
- return frappe.render_template("erpnext/selling/doctype/pos_closing_voucher/closing_voucher_details.html",
- {"data": self, "currency": currency})
-
-@frappe.whitelist()
-def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
- cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'])
- cashiers = [cashier for cashier in set(c['user'] for c in cashiers_list)]
- return [[c] for c in cashiers]
-
-def get_mode_of_payment_details(invoice_list):
- mode_of_payment_details = []
- invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list])
- if invoice_list:
- inv_mop_detail = frappe.db.sql("""select a.owner, a.posting_date,
- ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount
- from `tabSales Invoice` a, `tabSales Invoice Payment` b
- where a.name = b.parent
- and a.name in ({invoice_list_names})
- group by a.owner, a.posting_date, mode_of_payment
- union
- select a.owner,a.posting_date,
- ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_paid_amount) as paid_amount
- from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c
- where a.name = c.reference_name
- and b.name = c.parent
- and a.name in ({invoice_list_names})
- group by a.owner, a.posting_date, mode_of_payment
- union
- select a.owner, a.posting_date,
- ifnull(a.voucher_type,'') as mode_of_payment, sum(b.credit)
- from `tabJournal Entry` a, `tabJournal Entry Account` b
- where a.name = b.parent
- and a.docstatus = 1
- and b.reference_type = "Sales Invoice"
- and b.reference_name in ({invoice_list_names})
- group by a.owner, a.posting_date, mode_of_payment
- """.format(invoice_list_names=invoice_list_names), as_dict=1)
-
- inv_change_amount = frappe.db.sql("""select a.owner, a.posting_date,
- ifnull(b.mode_of_payment, '') as mode_of_payment, sum(a.base_change_amount) as change_amount
- from `tabSales Invoice` a, `tabSales Invoice Payment` b
- where a.name = b.parent
- and a.name in ({invoice_list_names})
- and b.mode_of_payment = 'Cash'
- and a.base_change_amount > 0
- group by a.owner, a.posting_date, mode_of_payment""".format(invoice_list_names=invoice_list_names), as_dict=1)
-
- for d in inv_change_amount:
- for det in inv_mop_detail:
- if det["owner"] == d["owner"] and det["posting_date"] == d["posting_date"] and det["mode_of_payment"] == d["mode_of_payment"]:
- paid_amount = det["paid_amount"] - d["change_amount"]
- det["paid_amount"] = paid_amount
-
- payment_details = defaultdict(int)
- for d in inv_mop_detail:
- payment_details[d.mode_of_payment] += d.paid_amount
-
- for m in payment_details:
- mode_of_payment_details.append({'name': m, 'amount': payment_details[m]})
-
- return mode_of_payment_details
-
-def get_tax_details(invoice_list):
- tax_breakup = []
- tax_details = defaultdict(int)
- for invoice in invoice_list:
- doc = frappe.get_doc("Sales Invoice", invoice.name)
- itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
-
- if itemised_tax:
- for a in itemised_tax:
- for b in itemised_tax[a]:
- for c in itemised_tax[a][b]:
- if c == 'tax_rate':
- tax_details[itemised_tax[a][b][c]] += itemised_tax[a][b]['tax_amount']
-
- for t in tax_details:
- tax_breakup.append({'rate': t, 'amount': tax_details[t]})
-
- return tax_breakup
-
-def get_sales_summary(invoice_list):
- net_total = sum(item['net_total'] for item in invoice_list)
- grand_total = sum(item['grand_total'] for item in invoice_list)
- total_qty = sum(item['pos_total_qty'] for item in invoice_list)
-
- return {'net_total': net_total, 'grand_total': grand_total, 'total_qty': total_qty}
-
-def get_company_currency(doc):
- currency = frappe.get_cached_value('Company', doc.company, "default_currency")
- return frappe.get_doc('Currency', currency)
-
-def get_invoices(filters):
- return frappe.db.sql("""select a.name, a.base_grand_total as grand_total,
- a.base_net_total as net_total, a.pos_total_qty
- from `tabSales Invoice` a
- where a.docstatus = 1 and a.posting_date >= %(from_date)s
- and a.posting_date <= %(to_date)s and a.company=%(company)s
- and a.pos_profile = %(pos_profile)s and a.is_pos = %(is_pos)s
- and a.owner = %(user)s""",
- filters, as_dict=1)
diff --git a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.py b/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.py
deleted file mode 100644
index 8899aaf..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher/test_pos_closing_voucher.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-import frappe
-import unittest
-from frappe.utils import nowdate
-from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
-from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
-
-class TestPOSClosingVoucher(unittest.TestCase):
- def test_pos_closing_voucher(self):
- old_user = frappe.session.user
- user = 'test@example.com'
- test_user = frappe.get_doc('User', user)
-
- roles = ("Accounts Manager", "Accounts User", "Sales Manager")
- test_user.add_roles(*roles)
- frappe.set_user(user)
-
- pos_profile = make_pos_profile()
- pos_profile.append('applicable_for_users', {
- 'default': 1,
- 'user': user
- })
-
- pos_profile.save()
-
- si1 = create_sales_invoice(is_pos=1, rate=3500, do_not_submit=1)
- si1.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500
- })
- si1.submit()
-
- si2 = create_sales_invoice(is_pos=1, rate=3200, do_not_submit=1)
- si2.append('payments', {
- 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
- })
- si2.submit()
-
- pcv_doc = create_pos_closing_voucher(user=user,
- pos_profile=pos_profile.name, collected_amount=6700)
-
- pcv_doc.get_closing_voucher_details()
-
- self.assertEqual(pcv_doc.total_quantity, 2)
- self.assertEqual(pcv_doc.net_total, 6700)
-
- payment = pcv_doc.payment_reconciliation[0]
- self.assertEqual(payment.mode_of_payment, 'Cash')
-
- si1.load_from_db()
- si1.cancel()
-
- si2.load_from_db()
- si2.cancel()
-
- test_user.load_from_db()
- test_user.remove_roles(*roles)
-
- frappe.set_user(old_user)
- frappe.db.sql("delete from `tabPOS Profile`")
-
-def create_pos_closing_voucher(**args):
- args = frappe._dict(args)
-
- doc = frappe.get_doc({
- 'doctype': 'POS Closing Voucher',
- 'period_start_date': args.period_start_date or nowdate(),
- 'period_end_date': args.period_end_date or nowdate(),
- 'posting_date': args.posting_date or nowdate(),
- 'company': args.company or "_Test Company",
- 'pos_profile': args.pos_profile,
- 'user': args.user or "Administrator",
- })
-
- doc.get_closing_voucher_details()
- if doc.get('payment_reconciliation'):
- doc.payment_reconciliation[0].collected_amount = (args.collected_amount or
- doc.payment_reconciliation[0].expected_amount)
-
- doc.save()
- return doc
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher_details/__init__.py b/erpnext/selling/doctype/pos_closing_voucher_details/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_details/__init__.py
+++ /dev/null
diff --git a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.json b/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.json
deleted file mode 100644
index a526884..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.json
+++ /dev/null
@@ -1,172 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-05-28 19:10:47.580174",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "mode_of_payment",
- "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": "Mode of Payment",
- "length": 0,
- "no_copy": 0,
- "options": "Mode of Payment",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0.0",
- "fieldname": "collected_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Collected Amount",
- "length": 0,
- "no_copy": 0,
- "options": "currency",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expected_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Expected 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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "difference",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Difference",
- "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
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-05-29 17:47:16.311557",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "POS Closing Voucher Details",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.py b/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.py
deleted file mode 100644
index 6bc323f..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_details/pos_closing_voucher_details.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-from frappe.model.document import Document
-
-class POSClosingVoucherDetails(Document):
- pass
diff --git a/erpnext/selling/doctype/pos_closing_voucher_invoices/__init__.py b/erpnext/selling/doctype/pos_closing_voucher_invoices/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_invoices/__init__.py
+++ /dev/null
diff --git a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.json b/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.json
deleted file mode 100644
index 7304550..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_invoices/pos_closing_voucher_invoices.json
+++ /dev/null
@@ -1,138 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-05-29 14:50:08.687453",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "invoice",
- "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": "Invoices",
- "length": 0,
- "no_copy": 0,
- "options": "Sales Invoice",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "qty_of_items",
- "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": "Quantity of Items",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "grand_total",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Grand Total",
- "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
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-05-29 17:46:46.539993",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "POS Closing Voucher Invoices",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher_taxes/__init__.py b/erpnext/selling/doctype/pos_closing_voucher_taxes/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_taxes/__init__.py
+++ /dev/null
diff --git a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.json b/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.json
deleted file mode 100644
index 3089e06..0000000
--- a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.json
+++ /dev/null
@@ -1,106 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-05-30 09:11:22.535470",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "rate",
- "fieldtype": "Percent",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Rate",
- "length": 0,
- "no_copy": 0,
- "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
- },
- {
- "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": 1,
- "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
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-05-30 09:11:22.535470",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "POS Closing Voucher Taxes",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py
index c8a7167..d3281f7 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.py
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.py
@@ -22,12 +22,14 @@
"""Validates, main Item is not a stock item"""
if frappe.db.get_value("Item", self.new_item_code, "is_stock_item"):
frappe.throw(_("Parent Item {0} must not be a Stock Item").format(self.new_item_code))
-
+
def validate_child_items(self):
for item in self.items:
if frappe.db.exists("Product Bundle", item.item_code):
- frappe.throw(_("Child Item should not be a Product Bundle. Please remove item `{0}` and save").format(item.item_code))
-
+ frappe.throw(_("Row #{0}: Child Item should not be a Product Bundle. Please remove Item {1} and Save").format(item.idx, frappe.bold(item.item_code)))
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_new_item_code(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json
index 8e21927..6d34c2a 100644
--- a/erpnext/selling/doctype/quotation/quotation.json
+++ b/erpnext/selling/doctype/quotation/quotation.json
@@ -654,6 +654,7 @@
"fieldname": "base_in_words",
"fieldtype": "Data",
"label": "In Words (Company Currency)",
+ "length": 240,
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -713,6 +714,7 @@
"fieldname": "in_words",
"fieldtype": "Data",
"label": "In Words",
+ "length": 240,
"oldfieldname": "in_words_export",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -930,7 +932,7 @@
"is_submittable": 1,
"links": [],
"max_attachments": 1,
- "modified": "2019-12-30 19:14:56.630270",
+ "modified": "2020-07-18 04:59:09.960118",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
diff --git a/erpnext/selling/doctype/quotation/quotation_list.js b/erpnext/selling/doctype/quotation/quotation_list.js
index 802c0ba..f425acf 100644
--- a/erpnext/selling/doctype/quotation/quotation_list.js
+++ b/erpnext/selling/doctype/quotation/quotation_list.js
@@ -3,13 +3,15 @@
"company", "currency", 'valid_till'],
onload: function(listview) {
- listview.page.fields_dict.quotation_to.get_query = function() {
- return {
- "filters": {
- "name": ["in", ["Customer", "Lead"]],
- }
+ if (listview.page.fields_dict.quotation_to) {
+ listview.page.fields_dict.quotation_to.get_query = function() {
+ return {
+ "filters": {
+ "name": ["in", ["Customer", "Lead"]],
+ }
+ };
};
- };
+ }
},
get_indicator: function(doc) {
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index ee6b429..b4c3d79 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -280,5 +280,3 @@
qo.submit()
return qo
-
-
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index b57c4f3..8fa56ac 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -1,6 +1,7 @@
{
"actions": [],
"allow_import": 1,
+ "allow_workflow": 1,
"autoname": "naming_series:",
"creation": "2013-06-18 12:39:59",
"doctype": "DocType",
@@ -143,11 +144,15 @@
{
"fieldname": "customer_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"options": "fa fa-user"
},
{
"fieldname": "column_break0",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Column Break",
"width": "50%"
},
@@ -157,6 +162,8 @@
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Title",
"no_copy": 1,
"print_hide": 1
@@ -164,6 +171,8 @@
{
"fieldname": "naming_series",
"fieldtype": "Select",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Series",
"no_copy": 1,
"oldfieldname": "naming_series",
@@ -177,6 +186,8 @@
"bold": 1,
"fieldname": "customer",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_global_search": 1,
"in_standard_filter": 1,
"label": "Customer",
@@ -192,6 +203,8 @@
"fetch_from": "customer.customer_name",
"fieldname": "customer_name",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_global_search": 1,
"label": "Customer Name",
"read_only": 1
@@ -200,6 +213,8 @@
"default": "Sales",
"fieldname": "order_type",
"fieldtype": "Select",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Order Type",
"oldfieldname": "order_type",
"oldfieldtype": "Select",
@@ -210,6 +225,8 @@
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Column Break",
"width": "50%"
},
@@ -217,6 +234,8 @@
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"ignore_user_permissions": 1,
"label": "Amended From",
"no_copy": 1,
@@ -230,6 +249,8 @@
{
"fieldname": "company",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_standard_filter": 1,
"label": "Company",
"oldfieldname": "company",
@@ -244,6 +265,8 @@
"default": "Today",
"fieldname": "transaction_date",
"fieldtype": "Date",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_standard_filter": 1,
"label": "Date",
"no_copy": 1,
@@ -258,6 +281,8 @@
"depends_on": "eval:!doc.skip_delivery_note",
"fieldname": "delivery_date",
"fieldtype": "Date",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_list_view": 1,
"label": "Delivery Date",
"no_copy": 1
@@ -266,6 +291,8 @@
"allow_on_submit": 1,
"fieldname": "po_no",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Customer's Purchase Order",
"oldfieldname": "po_no",
"oldfieldtype": "Data",
@@ -276,6 +303,8 @@
"depends_on": "eval:doc.po_no",
"fieldname": "po_date",
"fieldtype": "Date",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Customer's Purchase Order Date",
"oldfieldname": "po_date",
"oldfieldtype": "Date",
@@ -285,6 +314,8 @@
"fetch_from": "customer.tax_id",
"fieldname": "tax_id",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Tax Id",
"read_only": 1,
"width": "100px"
@@ -294,6 +325,8 @@
"depends_on": "customer",
"fieldname": "contact_info",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Address and Contact",
"options": "fa fa-bullhorn"
},
@@ -301,6 +334,8 @@
"allow_on_submit": 1,
"fieldname": "customer_address",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Customer Address",
"options": "Address",
"print_hide": 1
@@ -309,12 +344,16 @@
"allow_on_submit": 1,
"fieldname": "address_display",
"fieldtype": "Small Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Address",
"read_only": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Contact Person",
"options": "Contact",
"print_hide": 1
@@ -322,6 +361,8 @@
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_global_search": 1,
"label": "Contact",
"read_only": 1
@@ -329,6 +370,8 @@
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Mobile No",
"read_only": 1
},
@@ -336,6 +379,8 @@
"fieldname": "contact_email",
"fieldtype": "Data",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
@@ -344,24 +389,32 @@
{
"fieldname": "company_address_display",
"fieldtype": "Small Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Company Address",
"read_only": 1
},
{
"fieldname": "company_address",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Company Address Name",
"options": "Address"
},
{
"fieldname": "col_break46",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"width": "50%"
},
{
"allow_on_submit": 1,
"fieldname": "shipping_address_name",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Shipping Address Name",
"options": "Address",
"print_hide": 1
@@ -370,6 +423,8 @@
"allow_on_submit": 1,
"fieldname": "shipping_address",
"fieldtype": "Small Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Shipping Address",
"print_hide": 1,
"read_only": 1
@@ -378,6 +433,8 @@
"fieldname": "customer_group",
"fieldtype": "Link",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Customer Group",
"options": "Customer Group",
"print_hide": 1
@@ -385,6 +442,8 @@
{
"fieldname": "territory",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Territory",
"options": "Territory",
"print_hide": 1
@@ -393,6 +452,8 @@
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Currency and Price List",
"options": "fa fa-tag",
"print_hide": 1
@@ -400,6 +461,8 @@
{
"fieldname": "currency",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Currency",
"oldfieldname": "currency",
"oldfieldtype": "Select",
@@ -412,6 +475,8 @@
"description": "Rate at which customer's currency is converted to company's base currency",
"fieldname": "conversion_rate",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Exchange Rate",
"oldfieldname": "conversion_rate",
"oldfieldtype": "Currency",
@@ -423,11 +488,15 @@
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"width": "50%"
},
{
"fieldname": "selling_price_list",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Price List",
"oldfieldname": "price_list_name",
"oldfieldtype": "Select",
@@ -439,6 +508,8 @@
{
"fieldname": "price_list_currency",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
@@ -449,6 +520,8 @@
"description": "Rate at which Price list currency is converted to company's base currency",
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Price List Exchange Rate",
"precision": "9",
"print_hide": 1,
@@ -458,6 +531,8 @@
"default": "0",
"fieldname": "ignore_pricing_rule",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
@@ -465,11 +540,15 @@
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "set_warehouse",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Set Source Warehouse",
"options": "Warehouse",
"print_hide": 1
@@ -477,18 +556,24 @@
{
"fieldname": "items_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Section Break",
"options": "fa fa-shopping-cart"
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Scan Barcode"
},
{
"allow_bulk_edit": 1,
"fieldname": "items",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Items",
"oldfieldname": "sales_order_details",
"oldfieldtype": "Table",
@@ -498,32 +583,44 @@
{
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Pricing Rules"
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
"read_only": 1
},
{
"fieldname": "section_break_31",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "column_break_33a",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Quantity",
"read_only": 1
},
{
"fieldname": "base_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
@@ -532,6 +629,8 @@
{
"fieldname": "base_net_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Net Total (Company Currency)",
"oldfieldname": "net_total",
"oldfieldtype": "Currency",
@@ -542,11 +641,15 @@
},
{
"fieldname": "column_break_33",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total",
"options": "currency",
"read_only": 1
@@ -554,6 +657,8 @@
{
"fieldname": "net_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Net Total",
"options": "currency",
"print_hide": 1,
@@ -562,6 +667,8 @@
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Net Weight",
"print_hide": 1,
"read_only": 1
@@ -569,6 +676,8 @@
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Taxes and Charges",
"oldfieldtype": "Section Break",
"options": "fa fa-money"
@@ -576,17 +685,23 @@
{
"fieldname": "tax_category",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Tax Category",
"options": "Tax Category",
"print_hide": 1
},
{
"fieldname": "column_break_38",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Shipping Rule",
"oldfieldtype": "Button",
"options": "Shipping Rule",
@@ -594,11 +709,15 @@
},
{
"fieldname": "section_break_40",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "taxes_and_charges",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Taxes and Charges Template",
"oldfieldname": "charge",
"oldfieldtype": "Link",
@@ -608,6 +727,8 @@
{
"fieldname": "taxes",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Taxes and Charges",
"oldfieldname": "other_charges",
"oldfieldtype": "Table",
@@ -617,11 +738,15 @@
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Tax Breakup"
},
{
"fieldname": "other_charges_calculation",
"fieldtype": "Long Text",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Taxes and Charges Calculation",
"no_copy": 1,
"oldfieldtype": "HTML",
@@ -630,11 +755,15 @@
},
{
"fieldname": "section_break_43",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Taxes and Charges (Company Currency)",
"oldfieldname": "other_charges_total",
"oldfieldtype": "Currency",
@@ -645,11 +774,15 @@
},
{
"fieldname": "column_break_46",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
@@ -659,6 +792,8 @@
"fieldname": "loyalty_points_redemption",
"fieldtype": "Section Break",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Loyalty Points Redemption",
"print_hide": 1
},
@@ -666,6 +801,8 @@
"fieldname": "loyalty_points",
"fieldtype": "Int",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Loyalty Points",
"read_only": 1
},
@@ -673,6 +810,8 @@
"fieldname": "loyalty_amount",
"fieldtype": "Currency",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Loyalty Amount",
"print_hide": 1,
"read_only": 1
@@ -682,11 +821,15 @@
"collapsible_depends_on": "discount_amount",
"fieldname": "section_break_48",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Additional Discount and Coupon Code"
},
{
"fieldname": "coupon_code",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Coupon Code",
"options": "Coupon Code"
},
@@ -694,6 +837,8 @@
"default": "Grand Total",
"fieldname": "apply_discount_on",
"fieldtype": "Select",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
"print_hide": 1
@@ -701,6 +846,8 @@
{
"fieldname": "base_discount_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
@@ -708,17 +855,23 @@
},
{
"fieldname": "column_break_50",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Additional Discount Percentage",
"print_hide": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Additional Discount Amount",
"options": "currency",
"print_hide": 1
@@ -726,6 +879,8 @@
{
"fieldname": "totals",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Section Break",
"options": "fa fa-money",
"print_hide": 1
@@ -733,6 +888,8 @@
{
"fieldname": "base_grand_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Grand Total (Company Currency)",
"oldfieldname": "grand_total",
"oldfieldtype": "Currency",
@@ -744,6 +901,8 @@
{
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Rounding Adjustment (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
@@ -753,6 +912,8 @@
{
"fieldname": "base_rounded_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Rounded Total (Company Currency)",
"oldfieldname": "rounded_total",
"oldfieldtype": "Currency",
@@ -765,7 +926,10 @@
"description": "In Words will be visible once you save the Sales Order.",
"fieldname": "base_in_words",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "In Words (Company Currency)",
+ "length": 240,
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -775,6 +939,8 @@
{
"fieldname": "column_break3",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Column Break",
"print_hide": 1,
"width": "50%"
@@ -782,6 +948,8 @@
{
"fieldname": "grand_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_list_view": 1,
"label": "Grand Total",
"oldfieldname": "grand_total_export",
@@ -793,6 +961,8 @@
{
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Rounding Adjustment",
"no_copy": 1,
"options": "currency",
@@ -803,6 +973,8 @@
"bold": 1,
"fieldname": "rounded_total",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Rounded Total",
"oldfieldname": "rounded_total_export",
"oldfieldtype": "Currency",
@@ -813,7 +985,10 @@
{
"fieldname": "in_words",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "In Words",
+ "length": 240,
"oldfieldname": "in_words_export",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -823,6 +998,8 @@
{
"fieldname": "advance_paid",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Advance Paid",
"no_copy": 1,
"options": "party_account_currency",
@@ -834,6 +1011,8 @@
"collapsible_depends_on": "packed_items",
"fieldname": "packing_list",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Packing List",
"oldfieldtype": "Section Break",
"options": "fa fa-suitcase",
@@ -842,6 +1021,8 @@
{
"fieldname": "packed_items",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Packed Items",
"options": "Packed Item",
"print_hide": 1,
@@ -850,11 +1031,15 @@
{
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Payment Terms"
},
{
"fieldname": "payment_terms_template",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Payment Terms Template",
"options": "Payment Terms Template",
"print_hide": 1
@@ -862,6 +1047,8 @@
{
"fieldname": "payment_schedule",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
@@ -872,6 +1059,8 @@
"collapsible_depends_on": "terms",
"fieldname": "terms_section_break",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Terms and Conditions",
"oldfieldtype": "Section Break",
"options": "fa fa-legal"
@@ -879,6 +1068,8 @@
{
"fieldname": "tc_name",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Terms",
"oldfieldname": "tc_name",
"oldfieldtype": "Link",
@@ -888,6 +1079,8 @@
{
"fieldname": "terms",
"fieldtype": "Text Editor",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Terms and Conditions Details",
"oldfieldname": "terms",
"oldfieldtype": "Text Editor"
@@ -897,6 +1090,8 @@
"collapsible_depends_on": "project",
"fieldname": "more_info",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "More Information",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text",
@@ -905,6 +1100,8 @@
{
"fieldname": "inter_company_order_reference",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Inter Company Order Reference",
"options": "Purchase Order"
},
@@ -912,6 +1109,8 @@
"description": "Track this Sales Order against any Project",
"fieldname": "project",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Project",
"oldfieldname": "project",
"oldfieldtype": "Link",
@@ -921,6 +1120,8 @@
"fieldname": "party_account_currency",
"fieldtype": "Link",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Party Account Currency",
"no_copy": 1,
"options": "Currency",
@@ -929,11 +1130,15 @@
},
{
"fieldname": "column_break_77",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "source",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Source",
"oldfieldname": "source",
"oldfieldtype": "Select",
@@ -943,6 +1148,8 @@
{
"fieldname": "campaign",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Campaign",
"oldfieldname": "campaign",
"oldfieldtype": "Link",
@@ -953,11 +1160,15 @@
"collapsible": 1,
"fieldname": "printing_details",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Print Settings"
},
{
"fieldname": "language",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Print Language",
"print_hide": 1,
"read_only": 1
@@ -966,6 +1177,8 @@
"allow_on_submit": 1,
"fieldname": "letter_head",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Letter Head",
"oldfieldname": "letter_head",
"oldfieldtype": "Select",
@@ -975,6 +1188,8 @@
{
"fieldname": "column_break4",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"oldfieldtype": "Column Break",
"print_hide": 1,
"width": "50%"
@@ -983,6 +1198,8 @@
"allow_on_submit": 1,
"fieldname": "select_print_heading",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Print Heading",
"no_copy": 1,
"oldfieldname": "select_print_heading",
@@ -996,6 +1213,8 @@
"default": "0",
"fieldname": "group_same_items",
"fieldtype": "Check",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Group same items",
"print_hide": 1
},
@@ -1003,6 +1222,8 @@
"collapsible": 1,
"fieldname": "section_break_78",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Billing and Delivery Status",
"oldfieldtype": "Column Break",
"print_hide": 1,
@@ -1012,6 +1233,8 @@
"default": "Draft",
"fieldname": "status",
"fieldtype": "Select",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_list_view": 1,
"label": "Status",
"no_copy": 1,
@@ -1028,6 +1251,8 @@
"fieldname": "delivery_status",
"fieldtype": "Select",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_standard_filter": 1,
"label": "Delivery Status",
"no_copy": 1,
@@ -1039,6 +1264,8 @@
"description": "% of materials delivered against this Sales Order",
"fieldname": "per_delivered",
"fieldtype": "Percent",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_list_view": 1,
"label": "% Delivered",
"no_copy": 1,
@@ -1050,13 +1277,17 @@
},
{
"fieldname": "column_break_81",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"depends_on": "eval:!doc.__islocal",
"description": "% of materials billed against this Sales Order",
"fieldname": "per_billed",
"fieldtype": "Percent",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_list_view": 1,
"label": "% Amount Billed",
"no_copy": 1,
@@ -1070,6 +1301,8 @@
"fieldname": "billing_status",
"fieldtype": "Select",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_standard_filter": 1,
"label": "Billing Status",
"no_copy": 1,
@@ -1081,6 +1314,8 @@
"collapsible_depends_on": "commission_rate",
"fieldname": "sales_team_section_break",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Commission",
"oldfieldtype": "Section Break",
"options": "fa fa-group",
@@ -1089,6 +1324,8 @@
{
"fieldname": "sales_partner",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Partner",
"oldfieldname": "sales_partner",
"oldfieldtype": "Link",
@@ -1099,12 +1336,16 @@
{
"fieldname": "column_break7",
"fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"print_hide": 1,
"width": "50%"
},
{
"fieldname": "commission_rate",
"fieldtype": "Float",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Commission Rate",
"oldfieldname": "commission_rate",
"oldfieldtype": "Currency",
@@ -1114,6 +1355,8 @@
{
"fieldname": "total_commission",
"fieldtype": "Currency",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Total Commission",
"oldfieldname": "total_commission",
"oldfieldtype": "Currency",
@@ -1125,6 +1368,8 @@
"collapsible_depends_on": "sales_team",
"fieldname": "section_break1",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Team",
"print_hide": 1
},
@@ -1132,6 +1377,8 @@
"allow_on_submit": 1,
"fieldname": "sales_team",
"fieldtype": "Table",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Sales Team",
"oldfieldname": "sales_team",
"oldfieldtype": "Table",
@@ -1140,8 +1387,11 @@
},
{
"allow_on_submit": 1,
+ "collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Auto Repeat Section",
"no_copy": 1,
"print_hide": 1,
@@ -1151,6 +1401,8 @@
"allow_on_submit": 1,
"fieldname": "from_date",
"fieldtype": "Date",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "From Date",
"no_copy": 1
},
@@ -1158,16 +1410,22 @@
"allow_on_submit": 1,
"fieldname": "to_date",
"fieldtype": "Date",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "To Date",
"no_copy": 1
},
{
"fieldname": "column_break_108",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "hide_days": 1,
+ "hide_seconds": 1
},
{
"fieldname": "auto_repeat",
"fieldtype": "Link",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Auto Repeat",
"options": "Auto Repeat"
},
@@ -1176,11 +1434,15 @@
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Update Auto Repeat Reference"
},
{
"fieldname": "contact_phone",
"fieldtype": "Data",
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Phone",
"read_only": 1
},
@@ -1189,6 +1451,8 @@
"fieldname": "skip_delivery_note",
"fieldtype": "Check",
"hidden": 1,
+ "hide_days": 1,
+ "hide_seconds": 1,
"label": "Skip Delivery Note",
"print_hide": 1
}
@@ -1197,7 +1461,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-19 21:39:19.486684",
+ "modified": "2020-07-18 05:13:06.680696",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index ffb6635..f882898 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -888,6 +888,7 @@
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_supplier(doctype, txt, searchfield, start, page_len, filters):
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
if supp_master_name == "Supplier Name":
diff --git a/erpnext/selling/doctype/sales_order/test_records.json b/erpnext/selling/doctype/sales_order/test_records.json
index 6cbd6c2..8a090e6 100644
--- a/erpnext/selling/doctype/sales_order/test_records.json
+++ b/erpnext/selling/doctype/sales_order/test_records.json
@@ -1,39 +1,39 @@
[
{
"advance_paid": 0.0,
- "company": "_Test Company",
- "conversion_rate": 1.0,
- "currency": "INR",
- "customer": "_Test Customer",
- "customer_group": "_Test Customer Group",
- "customer_name": "_Test Customer",
- "doctype": "Sales Order",
- "base_grand_total": 1000.0,
- "grand_total": 1000.0,
- "naming_series": "_T-Sales Order-",
- "order_type": "Sales",
- "plc_conversion_rate": 1.0,
- "price_list_currency": "INR",
+ "company": "_Test Company",
+ "conversion_rate": 1.0,
+ "currency": "INR",
+ "customer": "_Test Customer",
+ "customer_group": "_Test Customer Group",
+ "customer_name": "_Test Customer",
+ "doctype": "Sales Order",
+ "base_grand_total": 1000.0,
+ "grand_total": 1000.0,
+ "naming_series": "_T-Sales Order-",
+ "order_type": "Sales",
+ "plc_conversion_rate": 1.0,
+ "price_list_currency": "INR",
"items": [
{
- "base_amount": 1000.0,
- "base_rate": 100.0,
- "description": "CPU",
- "doctype": "Sales Order Item",
- "item_code": "_Test Item Home Desktop 100",
- "item_name": "CPU",
- "delivery_date": "2013-02-23",
- "parentfield": "items",
- "qty": 10.0,
- "rate": 100.0,
+ "base_amount": 1000.0,
+ "base_rate": 100.0,
+ "description": "CPU",
+ "doctype": "Sales Order Item",
+ "item_code": "_Test Item",
+ "item_name": "_Test Item 1",
+ "delivery_date": "2013-02-23",
+ "parentfield": "items",
+ "qty": 10.0,
+ "rate": 100.0,
"warehouse": "_Test Warehouse - _TC",
"stock_uom": "_Test UOM",
"conversion_factor": 1.0,
"uom": "_Test UOM"
}
- ],
- "selling_price_list": "_Test Price List",
- "territory": "_Test Territory",
+ ],
+ "selling_price_list": "_Test Price List",
+ "territory": "_Test Territory",
"transaction_date": "2013-02-21"
}
]
\ No newline at end of file
diff --git a/erpnext/selling/module_onboarding/selling/selling.json b/erpnext/selling/module_onboarding/selling/selling.json
index 10a33c9..160208f 100644
--- a/erpnext/selling/module_onboarding/selling/selling.json
+++ b/erpnext/selling/module_onboarding/selling/selling.json
@@ -19,7 +19,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/selling",
"idx": 0,
"is_complete": 0,
- "modified": "2020-06-01 13:35:16.100512",
+ "modified": "2020-07-08 14:05:37.669753",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling",
@@ -47,8 +47,7 @@
"step": "Selling Settings"
}
],
- "subtitle": "Products, Sales, Analysis and more.",
+ "subtitle": "Products, Sales, Analysis, and more.",
"success_message": "The Selling Module is all set up!",
- "title": "Let's Set Up the Selling Module.",
- "user_can_dismiss": 1
+ "title": "Let's Set Up the Selling Module."
}
\ No newline at end of file
diff --git a/erpnext/selling/number_card/active_customers/active_customers.json b/erpnext/selling/number_card/active_customers/active_customers.json
new file mode 100644
index 0000000..3377634
--- /dev/null
+++ b/erpnext/selling/number_card/active_customers/active_customers.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-20 20:17:16.653866",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Customer",
+ "dynamic_filters_json": "",
+ "filters_json": "[[\"Customer\",\"disabled\",\"=\",\"0\"]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Active Customers",
+ "modified": "2020-07-22 14:20:32.268103",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Active Customers",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/selling/number_card/annual_sales/annual_sales.json b/erpnext/selling/number_card/annual_sales/annual_sales.json
new file mode 100644
index 0000000..8746ee4
--- /dev/null
+++ b/erpnext/selling/number_card/annual_sales/annual_sales.json
@@ -0,0 +1,22 @@
+{
+ "aggregate_function_based_on": "base_net_total",
+ "creation": "2020-07-20 20:17:16.568132",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Sales Order",
+ "dynamic_filters_json": "[[\"Sales Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Sales Order\",\"status\",\"not in\",[\"Draft\",\"Cancelled\",\"Closed\",null],false],[\"Sales Order\",\"docstatus\",\"=\",\"1\",false],[\"Sales Order\",\"modified\",\"Timespan\",\"this year\",false]]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Annual Sales",
+ "modified": "2020-07-22 16:56:33.747156",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Annual Sales",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/selling/number_card/sales_orders_to_bill/sales_orders_to_bill.json b/erpnext/selling/number_card/sales_orders_to_bill/sales_orders_to_bill.json
new file mode 100644
index 0000000..27fea45
--- /dev/null
+++ b/erpnext/selling/number_card/sales_orders_to_bill/sales_orders_to_bill.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-20 20:17:16.625001",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Sales Order",
+ "dynamic_filters_json": "[[\"Sales Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Sales Order\",\"status\",\"in\",[\"To Deliver and Bill\",\"To Bill\",null],false],[\"Sales Order\",\"docstatus\",\"=\",\"1\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Sales Orders to Bill",
+ "modified": "2020-07-22 14:20:09.918626",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Sales Orders to Bill",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/selling/number_card/sales_orders_to_deliver/sales_orders_to_deliver.json b/erpnext/selling/number_card/sales_orders_to_deliver/sales_orders_to_deliver.json
new file mode 100644
index 0000000..6e19cf4
--- /dev/null
+++ b/erpnext/selling/number_card/sales_orders_to_deliver/sales_orders_to_deliver.json
@@ -0,0 +1,21 @@
+{
+ "creation": "2020-07-20 20:17:16.596857",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Sales Order",
+ "dynamic_filters_json": "[[\"Sales Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
+ "filters_json": "[[\"Sales Order\",\"status\",\"in\",[\"To Deliver and Bill\",\"To Deliver\",null],false],[\"Sales Order\",\"docstatus\",\"=\",\"1\",false]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Sales Orders to Deliver",
+ "modified": "2020-07-22 14:19:28.833784",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Sales Orders to Deliver",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
index 557c905..9457dee 100644
--- a/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
+++ b/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
@@ -8,13 +8,13 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-19 18:54:19.383397",
+ "modified": "2020-07-04 12:33:16.970031",
"modified_by": "Administrator",
"name": "Setup your Warehouse",
"owner": "Administrator",
"path": "Tree/Warehouse",
"reference_document": "Warehouse",
"show_full_form": 0,
- "title": "Setup your Warehouse",
+ "title": "Set up your Warehouse",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/onscan.js b/erpnext/selling/page/point_of_sale/onscan.js
new file mode 100644
index 0000000..428dc75
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/onscan.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t()):e.onScan=t()}(this,function(){var d={attachTo:function(e,t){if(void 0!==e.scannerDetectionData)throw new Error("onScan.js is already initialized for DOM element "+e);var n={onScan:function(e,t){},onScanError:function(e){},onKeyProcess:function(e,t){},onKeyDetect:function(e,t){},onPaste:function(e,t){},keyCodeMapper:function(e){return d.decodeKeyEvent(e)},onScanButtonLongPress:function(){},scanButtonKeyCode:!1,scanButtonLongPressTime:500,timeBeforeScanTest:100,avgTimeByChar:30,minLength:6,suffixKeyCodes:[9,13],prefixKeyCodes:[],ignoreIfFocusOn:!1,stopPropagation:!1,preventDefault:!1,captureEvents:!1,reactToKeydown:!0,reactToPaste:!1,singleScanQty:1};return t=this._mergeOptions(n,t),e.scannerDetectionData={options:t,vars:{firstCharTime:0,lastCharTime:0,accumulatedString:"",testTimer:!1,longPressTimeStart:0,longPressed:!1}},!0===t.reactToPaste&&e.addEventListener("paste",this._handlePaste,t.captureEvents),!1!==t.scanButtonKeyCode&&e.addEventListener("keyup",this._handleKeyUp,t.captureEvents),!0!==t.reactToKeydown&&!1===t.scanButtonKeyCode||e.addEventListener("keydown",this._handleKeyDown,t.captureEvents),this},detachFrom:function(e){e.scannerDetectionData.options.reactToPaste&&e.removeEventListener("paste",this._handlePaste),!1!==e.scannerDetectionData.options.scanButtonKeyCode&&e.removeEventListener("keyup",this._handleKeyUp),e.removeEventListener("keydown",this._handleKeyDown),e.scannerDetectionData=void 0},getOptions:function(e){return e.scannerDetectionData.options},setOptions:function(e,t){switch(e.scannerDetectionData.options.reactToPaste){case!0:!1===t.reactToPaste&&e.removeEventListener("paste",this._handlePaste);break;case!1:!0===t.reactToPaste&&e.addEventListener("paste",this._handlePaste)}switch(e.scannerDetectionData.options.scanButtonKeyCode){case!1:!1!==t.scanButtonKeyCode&&e.addEventListener("keyup",this._handleKeyUp);break;default:!1===t.scanButtonKeyCode&&e.removeEventListener("keyup",this._handleKeyUp)}return e.scannerDetectionData.options=this._mergeOptions(e.scannerDetectionData.options,t),this._reinitialize(e),this},decodeKeyEvent:function(e){var t=this._getNormalizedKeyNum(e);switch(!0){case 48<=t&&t<=90:case 106<=t&&t<=111:if(void 0!==e.key&&""!==e.key)return e.key;var n=String.fromCharCode(t);switch(e.shiftKey){case!1:n=n.toLowerCase();break;case!0:n=n.toUpperCase()}return n;case 96<=t&&t<=105:return t-96}return""},simulate:function(e,t){return this._reinitialize(e),Array.isArray(t)?t.forEach(function(e){var t={};"object"!=typeof e&&"function"!=typeof e||null===e?t.keyCode=parseInt(e):t=e;var n=new KeyboardEvent("keydown",t);document.dispatchEvent(n)}):this._validateScanCode(e,t),this},_reinitialize:function(e){var t=e.scannerDetectionData.vars;t.firstCharTime=0,t.lastCharTime=0,t.accumulatedString=""},_isFocusOnIgnoredElement:function(e){var t=e.scannerDetectionData.options.ignoreIfFocusOn;if(!t)return!1;var n=document.activeElement;if(Array.isArray(t)){for(var a=0;a<t.length;a++)if(!0===n.matches(t[a]))return!0}else if(n.matches(t))return!0;return!1},_validateScanCode:function(e,t){var n,a=e.scannerDetectionData,i=a.options,o=a.options.singleScanQty,r=a.vars.firstCharTime,s=a.vars.lastCharTime,c={};switch(!0){case t.length<i.minLength:c={message:"Receieved code is shorter then minimal length"};break;case s-r>t.length*i.avgTimeByChar:c={message:"Receieved code was not entered in time"};break;default:return i.onScan.call(e,t,o),n=new CustomEvent("scan",{detail:{scanCode:t,qty:o}}),e.dispatchEvent(n),d._reinitialize(e),!0}return c.scanCode=t,c.scanDuration=s-r,c.avgTimeByChar=i.avgTimeByChar,c.minLength=i.minLength,i.onScanError.call(e,c),n=new CustomEvent("scanError",{detail:c}),e.dispatchEvent(n),d._reinitialize(e),!1},_mergeOptions:function(e,t){var n,a={};for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(a[n]=e[n]);for(n in t)Object.prototype.hasOwnProperty.call(t,n)&&(a[n]=t[n]);return a},_getNormalizedKeyNum:function(e){return e.which||e.keyCode},_handleKeyDown:function(e){var t=d._getNormalizedKeyNum(e),n=this.scannerDetectionData.options,a=this.scannerDetectionData.vars,i=!1;if(!1!==n.onKeyDetect.call(this,t,e)&&!d._isFocusOnIgnoredElement(this))if(!1===n.scanButtonKeyCode||t!=n.scanButtonKeyCode){switch(!0){case a.firstCharTime&&-1!==n.suffixKeyCodes.indexOf(t):e.preventDefault(),e.stopImmediatePropagation(),i=!0;break;case!a.firstCharTime&&-1!==n.prefixKeyCodes.indexOf(t):e.preventDefault(),e.stopImmediatePropagation(),i=!1;break;default:var o=n.keyCodeMapper.call(this,e);if(null===o)return;a.accumulatedString+=o,n.preventDefault&&e.preventDefault(),n.stopPropagation&&e.stopImmediatePropagation(),i=!1}a.firstCharTime||(a.firstCharTime=Date.now()),a.lastCharTime=Date.now(),a.testTimer&&clearTimeout(a.testTimer),i?(d._validateScanCode(this,a.accumulatedString),a.testTimer=!1):a.testTimer=setTimeout(d._validateScanCode,n.timeBeforeScanTest,this,a.accumulatedString),n.onKeyProcess.call(this,o,e)}else a.longPressed||(a.longPressTimer=setTimeout(n.onScanButtonLongPress,n.scanButtonLongPressTime,this),a.longPressed=!0)},_handlePaste:function(e){if(!d._isFocusOnIgnoredElement(this)){e.preventDefault(),oOptions.stopPropagation&&e.stopImmediatePropagation();var t=(event.clipboardData||window.clipboardData).getData("text");this.scannerDetectionData.options.onPaste.call(this,t,event);var n=this.scannerDetectionData.vars;n.firstCharTime=0,n.lastCharTime=0,d._validateScanCode(this,t)}},_handleKeyUp:function(e){d._isFocusOnIgnoredElement(this)||d._getNormalizedKeyNum(e)==this.scannerDetectionData.options.scanButtonKeyCode&&(clearTimeout(this.scannerDetectionData.vars.longPressTimer),this.scannerDetectionData.vars.longPressed=!1)},isScanInProgressFor:function(e){return 0<e.scannerDetectionData.vars.firstCharTime}};return d});
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index 7011cf9..2ce0b27 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -1,5 +1,6 @@
/* global Clusterize */
-frappe.provide('erpnext.pos');
+frappe.provide('erpnext.PointOfSale');
+{% include "erpnext/selling/page/point_of_sale/pos_controller.js" %}
frappe.provide('erpnext.queries');
frappe.pages['point-of-sale'].on_page_load = function(wrapper) {
@@ -8,1988 +9,7 @@
title: __('Point of Sale'),
single_column: true
});
-
- frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => {
- if (r && !cint(r.use_pos_in_offline_mode)) {
- // online
- wrapper.pos = new erpnext.pos.PointOfSale(wrapper);
- window.cur_pos = wrapper.pos;
- } else {
- // offline
- frappe.flags.is_offline = true;
- frappe.set_route('pos');
- }
- });
-};
-
-frappe.pages['point-of-sale'].refresh = function(wrapper) {
- if (wrapper.pos) {
- wrapper.pos.make_new_invoice();
- }
-
- if (frappe.flags.is_offline) {
- frappe.set_route('pos');
- }
-}
-
-erpnext.pos.PointOfSale = class PointOfSale {
- constructor(wrapper) {
- this.wrapper = $(wrapper).find('.layout-main-section');
- this.page = wrapper.page;
-
- const assets = [
- 'assets/erpnext/js/pos/clusterize.js',
- 'assets/erpnext/css/pos.css'
- ];
-
- frappe.require(assets, () => {
- this.make();
- });
- }
-
- make() {
- return frappe.run_serially([
- () => frappe.dom.freeze(),
- () => {
- this.prepare_dom();
- this.prepare_menu();
- this.set_online_status();
- },
- () => this.make_new_invoice(),
- () => {
- if(!this.frm.doc.company) {
- this.setup_company()
- .then((company) => {
- this.frm.doc.company = company;
- this.get_pos_profile();
- });
- }
- },
- () => {
- frappe.dom.unfreeze();
- },
- () => this.page.set_title(__('Point of Sale'))
- ]);
- }
-
- get_pos_profile() {
- return frappe.xcall("erpnext.stock.get_item_details.get_pos_profile",
- {'company': this.frm.doc.company})
- .then((r) => {
- if(r) {
- this.frm.doc.pos_profile = r.name;
- this.set_pos_profile_data()
- .then(() => {
- this.on_change_pos_profile();
- });
- } else {
- this.raise_exception_for_pos_profile();
- }
- });
- }
-
- set_online_status() {
- this.connection_status = false;
- this.page.set_indicator(__("Offline"), "grey");
- frappe.call({
- method: "frappe.handler.ping",
- callback: r => {
- if (r.message) {
- this.connection_status = true;
- this.page.set_indicator(__("Online"), "green");
- }
- }
- });
- }
-
- raise_exception_for_pos_profile() {
- setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000);
- frappe.throw(__("POS Profile is required to use Point-of-Sale"));
- }
-
- prepare_dom() {
- this.wrapper.append(`
- <div class="pos">
- <section class="cart-container">
-
- </section>
- <section class="item-container">
-
- </section>
- </div>
- `);
- }
-
- make_cart() {
- this.cart = new POSCart({
- frm: this.frm,
- wrapper: this.wrapper.find('.cart-container'),
- events: {
- on_customer_change: (customer) => {
- this.frm.set_value('customer', customer);
- },
- on_field_change: (item_code, field, value, batch_no) => {
- this.update_item_in_cart(item_code, field, value, batch_no);
- },
- on_numpad: (value) => {
- if (value == __('Pay')) {
- if (!this.payment) {
- this.make_payment_modal();
- } else {
- this.frm.doc.payments.map(p => {
- this.payment.dialog.set_value(p.mode_of_payment, p.amount);
- });
-
- this.payment.set_title();
- }
- this.payment.open_modal();
- }
- },
- on_select_change: () => {
- this.cart.numpad.set_inactive();
- this.set_form_action();
- },
- get_item_details: (item_code) => {
- return this.items.get(item_code);
- },
- get_loyalty_details: () => {
- var me = this;
- if (this.frm.doc.customer) {
- frappe.call({
- method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details",
- args: {
- "customer": me.frm.doc.customer,
- "expiry_date": me.frm.doc.posting_date,
- "company": me.frm.doc.company,
- "silent": true
- },
- callback: function(r) {
- if (r.message.loyalty_program && r.message.loyalty_points) {
- me.cart.events.set_loyalty_details(r.message, true);
- }
- if (!r.message.loyalty_program) {
- var loyalty_details = {
- loyalty_points: 0,
- loyalty_program: '',
- expense_account: '',
- cost_center: ''
- }
- me.cart.events.set_loyalty_details(loyalty_details, false);
- }
- }
- });
- }
- },
- set_loyalty_details: (details, view_status) => {
- if (view_status) {
- this.cart.available_loyalty_points.$wrapper.removeClass("hide");
- } else {
- this.cart.available_loyalty_points.$wrapper.addClass("hide");
- }
- this.cart.available_loyalty_points.set_value(details.loyalty_points);
- this.cart.available_loyalty_points.refresh_input();
- this.frm.set_value("loyalty_program", details.loyalty_program);
- this.frm.set_value("loyalty_redemption_account", details.expense_account);
- this.frm.set_value("loyalty_redemption_cost_center", details.cost_center);
- }
- }
- });
-
- frappe.ui.form.on('Sales Invoice', 'selling_price_list', (frm) => {
- if(this.items && frm.doc.pos_profile) {
- this.items.reset_items();
- }
- })
- }
-
- toggle_editing(flag) {
- let disabled;
- if (flag !== undefined) {
- disabled = !flag;
- } else {
- disabled = this.frm.doc.docstatus == 1 ? true: false;
- }
- const pointer_events = disabled ? 'none' : 'inherit';
-
- this.wrapper.find('input, button, select').prop("disabled", disabled);
- this.wrapper.find('.number-pad-container').toggleClass("hide", disabled);
-
- this.wrapper.find('.cart-container').css('pointer-events', pointer_events);
- this.wrapper.find('.item-container').css('pointer-events', pointer_events);
-
- this.page.clear_actions();
- }
-
- make_items() {
- this.items = new POSItems({
- wrapper: this.wrapper.find('.item-container'),
- frm: this.frm,
- events: {
- update_cart: (item, field, value) => {
- if(!this.frm.doc.customer) {
- frappe.throw(__('Please select a customer'));
- }
- this.update_item_in_cart(item, field, value);
- this.cart && this.cart.unselect_all();
- }
- }
- });
- }
-
- update_item_in_cart(item_code, field='qty', value=1, batch_no) {
- frappe.dom.freeze();
- if(this.cart.exists(item_code, batch_no)) {
- const search_field = batch_no ? 'batch_no' : 'item_code';
- const search_value = batch_no || item_code;
- const item = this.frm.doc.items.find(i => i[search_field] === search_value);
- frappe.flags.hide_serial_batch_dialog = false;
-
- if (typeof value === 'string' && !in_list(['serial_no', 'batch_no'], field)) {
- // value can be of type '+1' or '-1'
- value = item[field] + flt(value);
- }
-
- if(field === 'serial_no') {
- value = item.serial_no + '\n'+ value;
- }
-
- // if actual_batch_qty and actual_qty if there is only one batch. In such
- // a case, no point showing the dialog
- const show_dialog = item.has_serial_no || item.has_batch_no;
-
- if (show_dialog && field == 'qty' && ((!item.batch_no && item.has_batch_no) ||
- (item.has_serial_no) || (item.actual_batch_qty != item.actual_qty)) ) {
- this.select_batch_and_serial_no(item);
- } else {
- this.update_item_in_frm(item, field, value)
- .then(() => {
- frappe.dom.unfreeze();
- frappe.run_serially([
- () => {
- let items = this.frm.doc.items.map(item => item.name);
- if (items && items.length > 0 && items.includes(item.name)) {
- this.frm.doc.items.forEach(item_row => {
- // update cart
- this.on_qty_change(item_row);
- });
- } else {
- this.on_qty_change(item);
- }
- },
- () => this.post_qty_change(item)
- ]);
- });
- }
- return;
- }
-
- let args = { item_code: item_code };
- if (in_list(['serial_no', 'batch_no'], field)) {
- args[field] = value;
- }
-
- // add to cur_frm
- const item = this.frm.add_child('items', args);
- frappe.flags.hide_serial_batch_dialog = true;
-
- frappe.run_serially([
- () => {
- return this.frm.script_manager.trigger('item_code', item.doctype, item.name)
- .then(() => {
- this.frm.script_manager.trigger('qty', item.doctype, item.name)
- .then(() => {
- frappe.run_serially([
- () => {
- let items = this.frm.doc.items.map(i => i.name);
- if (items && items.length > 0 && items.includes(item.name)) {
- this.frm.doc.items.forEach(item_row => {
- // update cart
- this.on_qty_change(item_row);
- });
- } else {
- this.on_qty_change(item);
- }
- },
- () => this.post_qty_change(item)
- ]);
- });
- });
- },
- () => {
- const show_dialog = item.has_serial_no || item.has_batch_no;
-
- // if actual_batch_qty and actual_qty if then there is only one batch. In such
- // a case, no point showing the dialog
- if (show_dialog && field == 'qty' && ((!item.batch_no && item.has_batch_no) ||
- (item.has_serial_no) || (item.actual_batch_qty != item.actual_qty)) ) {
- // check has serial no/batch no and update cart
- this.select_batch_and_serial_no(item);
- }
- }
- ]);
- }
-
- on_qty_change(item) {
- frappe.run_serially([
- () => this.update_cart_data(item),
- ]);
- }
-
- post_qty_change(item) {
- this.cart.update_taxes_and_totals();
- this.cart.update_grand_total();
- this.cart.update_qty_total();
- this.cart.scroll_to_item(item.item_code);
- this.set_form_action();
- }
-
- select_batch_and_serial_no(row) {
- frappe.dom.unfreeze();
-
- erpnext.show_serial_batch_selector(this.frm, row, () => {
- this.frm.doc.items.forEach(item => {
- this.update_item_in_frm(item, 'qty', item.qty)
- .then(() => {
- // update cart
- frappe.run_serially([
- () => {
- if (item.qty === 0) {
- frappe.model.clear_doc(item.doctype, item.name);
- }
- },
- () => this.update_cart_data(item),
- () => this.post_qty_change(item)
- ]);
- });
- })
- }, () => {
- this.on_close(row);
- }, true);
- }
-
- on_close(item) {
- if (!this.cart.exists(item.item_code, item.batch_no) && item.qty) {
- frappe.model.clear_doc(item.doctype, item.name);
- }
- }
-
- update_cart_data(item) {
- this.cart.add_item(item);
- frappe.dom.unfreeze();
- }
-
- update_item_in_frm(item, field, value) {
- if (field == 'qty' && value < 0) {
- frappe.msgprint(__("Quantity must be positive"));
- value = item.qty;
- } else {
- if (in_list(["qty", "serial_no", "batch"], field)) {
- item[field] = value;
- if (field == "serial_no" && value) {
- let serial_nos = value.split("\n");
- item["qty"] = serial_nos.filter(d => {
- return d!=="";
- }).length;
- }
- } else {
- return frappe.model.set_value(item.doctype, item.name, field, value);
- }
- }
-
- return this.frm.script_manager.trigger('qty', item.doctype, item.name)
- .then(() => {
- if (field === 'qty' && item.qty === 0) {
- frappe.model.clear_doc(item.doctype, item.name);
- }
- })
-
- return Promise.resolve();
- }
-
- make_payment_modal() {
- this.payment = new Payment({
- frm: this.frm,
- events: {
- submit_form: () => {
- this.submit_sales_invoice();
- }
- }
- });
- }
-
- submit_sales_invoice() {
- this.frm.savesubmit()
- .then((r) => {
- if (r && r.doc) {
- this.frm.doc.docstatus = r.doc.docstatus;
- frappe.show_alert({
- indicator: 'green',
- message: __(`Sales invoice ${r.doc.name} created succesfully`)
- });
-
- this.toggle_editing();
- this.set_form_action();
- this.set_primary_action_in_modal();
- }
- });
- }
-
- set_primary_action_in_modal() {
- if (!this.frm.msgbox) {
- this.frm.msgbox = frappe.msgprint(
- `<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
- ${__('Print')}</a>
- <a class="btn btn-default">
- ${__('New')}</a>`
- );
-
- $(this.frm.msgbox.body).find('.btn-default').on('click', () => {
- this.frm.msgbox.hide();
- this.make_new_invoice();
- })
- }
- }
-
- change_pos_profile() {
- return new Promise((resolve) => {
- const on_submit = ({ company, pos_profile, set_as_default }) => {
- if (pos_profile) {
- this.pos_profile = pos_profile;
- }
-
- if (set_as_default) {
- frappe.call({
- method: "erpnext.accounts.doctype.pos_profile.pos_profile.set_default_profile",
- args: {
- 'pos_profile': pos_profile,
- 'company': company
- }
- }).then(() => {
- this.on_change_pos_profile();
- });
- } else {
- this.on_change_pos_profile();
- }
- }
-
-
- let me = this;
-
- var dialog = frappe.prompt([{
- fieldtype: 'Link',
- label: __('Company'),
- options: 'Company',
- fieldname: 'company',
- default: me.frm.doc.company,
- reqd: 1,
- onchange: function(e) {
- me.get_default_pos_profile(this.value).then((r) => {
- dialog.set_value('pos_profile', (r && r.name)? r.name : '');
- });
- }
- },
- {
- fieldtype: 'Link',
- label: __('POS Profile'),
- options: 'POS Profile',
- fieldname: 'pos_profile',
- default: me.frm.doc.pos_profile,
- reqd: 1,
- get_query: () => {
- return {
- query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
- filters: {
- company: dialog.get_value('company')
- }
- };
- }
- }, {
- fieldtype: 'Check',
- label: __('Set as default'),
- fieldname: 'set_as_default'
- }],
- on_submit,
- __('Select POS Profile')
- );
- });
- }
-
- on_change_pos_profile() {
- return frappe.run_serially([
- () => this.make_sales_invoice_frm(),
- () => {
- this.frm.doc.pos_profile = this.pos_profile;
- this.set_pos_profile_data()
- .then(() => {
- this.reset_cart();
- if (this.items) {
- this.items.reset_items();
- }
- });
- }
- ]);
- }
-
- get_default_pos_profile(company) {
- return frappe.xcall("erpnext.stock.get_item_details.get_pos_profile",
- {'company': company})
- }
-
- setup_company() {
- return new Promise(resolve => {
- if(!this.frm.doc.company) {
- frappe.prompt({fieldname:"company", options: "Company", fieldtype:"Link",
- label: __("Select Company"), reqd: 1}, (data) => {
- this.company = data.company;
- resolve(this.company);
- }, __("Select Company"));
- } else {
- resolve();
- }
- })
- }
-
- make_new_invoice() {
- return frappe.run_serially([
- () => this.make_sales_invoice_frm(),
- () => this.set_pos_profile_data(),
- () => {
- if (this.cart) {
- this.cart.frm = this.frm;
- this.cart.reset();
- this.cart.reset_pos_field_value();
- } else {
- this.make_items();
- this.make_cart();
- }
- this.toggle_editing(true);
- },
- ]);
- }
-
- reset_cart() {
- this.cart.frm = this.frm;
- this.cart.reset();
- this.items.reset_search_field();
- }
-
- make_sales_invoice_frm() {
- const doctype = 'Sales Invoice';
- return new Promise(resolve => {
- if (this.frm) {
- this.frm = get_frm(this.frm);
- if(this.company) {
- this.frm.doc.company = this.company;
- }
-
- resolve();
- } else {
- frappe.model.with_doctype(doctype, () => {
- this.frm = get_frm();
- resolve();
- });
- }
- });
-
- function get_frm(_frm) {
- const page = $('<div>');
- const frm = _frm || new frappe.ui.form.Form(doctype, page, false);
- const name = frappe.model.make_new_doc_and_get_name(doctype, true);
- frm.refresh(name);
- frm.doc.items = [];
- frm.doc.is_pos = 1;
-
- return frm;
- }
- }
-
- set_pos_profile_data() {
- if (this.company) {
- this.frm.doc.company = this.company;
- }
-
- if (!this.frm.doc.company) {
- return;
- }
-
- return new Promise(resolve => {
- return this.frm.call({
- doc: this.frm.doc,
- method: "set_missing_values",
- }).then((r) => {
- if(!r.exc) {
- if (!this.frm.doc.pos_profile) {
- frappe.dom.unfreeze();
- this.raise_exception_for_pos_profile();
- }
- this.frm.script_manager.trigger("update_stock");
- frappe.model.set_default_values(this.frm.doc);
- this.frm.cscript.calculate_taxes_and_totals();
-
- if (r.message) {
- this.frm.meta.default_print_format = r.message.print_format || "";
- this.frm.allow_edit_rate = r.message.allow_edit_rate;
- this.frm.allow_edit_discount = r.message.allow_edit_discount;
- this.frm.doc.campaign = r.message.campaign;
- this.frm.allow_print_before_pay = r.message.allow_print_before_pay;
- }
- }
-
- resolve();
- });
- });
- }
-
- prepare_menu() {
- var me = this;
- this.page.clear_menu();
-
- this.page.add_menu_item(__("Form View"), function () {
- frappe.model.sync(me.frm.doc);
- frappe.set_route("Form", me.frm.doc.doctype, me.frm.doc.name);
- });
-
- this.page.add_menu_item(__("POS Profile"), function () {
- frappe.set_route('List', 'POS Profile');
- });
-
- this.page.add_menu_item(__('POS Settings'), function() {
- frappe.set_route('Form', 'POS Settings');
- });
-
- this.page.add_menu_item(__('Change POS Profile'), function() {
- me.change_pos_profile();
- });
- this.page.add_menu_item(__('Close the POS'), function() {
- var voucher = frappe.model.get_new_doc('POS Closing Voucher');
- voucher.pos_profile = me.frm.doc.pos_profile;
- voucher.user = frappe.session.user;
- voucher.company = me.frm.doc.company;
- voucher.period_start_date = me.frm.doc.posting_date;
- voucher.period_end_date = me.frm.doc.posting_date;
- voucher.posting_date = me.frm.doc.posting_date;
- frappe.set_route('Form', 'POS Closing Voucher', voucher.name);
- });
- }
-
- set_form_action() {
- if(this.frm.doc.docstatus == 1 || (this.frm.allow_print_before_pay == 1 && this.frm.doc.items.length > 0)){
- this.page.set_secondary_action(__("Print"), async() => {
- if(this.frm.doc.docstatus != 1 ){
- await this.frm.save();
- }
- this.frm.print_preview.printit(true);
- });
- }
- if(this.frm.doc.items.length == 0){
- this.page.clear_secondary_action();
- }
-
- if (this.frm.doc.docstatus == 1) {
- this.page.set_primary_action(__("New"), () => {
- this.make_new_invoice();
- });
- this.page.add_menu_item(__("Email"), () => {
- this.frm.email_doc();
- });
- }
- }
-};
-
-const [Qty,Disc,Rate,Del,Pay] = [__("Qty"), __('Disc'), __('Rate'), __('Del'), __('Pay')];
-
-class POSCart {
- constructor({frm, wrapper, events}) {
- this.frm = frm;
- this.item_data = {};
- this.wrapper = wrapper;
- this.events = events;
- this.make();
- this.bind_events();
- }
-
- make() {
- this.make_dom();
- this.make_customer_field();
- this.make_pos_fields();
- this.make_loyalty_points();
- this.make_numpad();
- }
-
- make_dom() {
- this.wrapper.append(`
- <div class="pos-cart">
- <div class="customer-field">
- </div>
- <div class="pos-field-section" style="margin-bottom:12px; display:none">
- <a class="h6 uppercase more-fields-section" disabled> ${__("More Information")} </a>
- <i class="octicon octicon-chevron-down pos-fields-octicon collapse-indicator"
- style="color:#cacaca; cursor: pointer"></i>
- <div class="pos-fields" style ="margin-top:12px">
- </div>
- </div>
- <div class="cart-wrapper">
- <div class="list-item-table">
- <div class="list-item list-item--head">
- <div class="list-item__content list-item__content--flex-1.5 text-muted">${__('Item Name')}</div>
- <div class="list-item__content text-muted text-right">${__('Quantity')}</div>
- <div class="list-item__content text-muted text-right">${__('Discount')}</div>
- <div class="list-item__content text-muted text-right">${__('Rate')}</div>
- </div>
- <div class="cart-items">
- <div class="empty-state">
- <span>${__('No Items added to cart')}</span>
- </div>
- </div>
- <div class="taxes-and-totals">
- ${this.get_taxes_and_totals()}
- </div>
- <div class="discount-amount">`+
- (!this.frm.allow_edit_discount ? `` : `${this.get_discount_amount()}`)+
- `</div>
- <div class="grand-total">
- ${this.get_grand_total()}
- </div>
- <div class="quantity-total">
- ${this.get_item_qty_total()}
- </div>
- </div>
- </div>
- <div class="row">
- <div class="number-pad-container col-sm-6"></div>
- <div class="col-sm-6 loyalty-program-section">
- <div class="loyalty-program-field"> </div>
- </div>
- </div>
- </div>
- `);
-
-
- this.$cart_items = this.wrapper.find('.cart-items');
- this.$empty_state = this.wrapper.find('.cart-items .empty-state');
- this.$taxes_and_totals = this.wrapper.find('.taxes-and-totals');
- this.$discount_amount = this.wrapper.find('.discount-amount');
- this.$grand_total = this.wrapper.find('.grand-total');
- this.$qty_total = this.wrapper.find('.quantity-total');
- // this.$loyalty_button = this.wrapper.find('.loyalty-button');
-
- // this.$loyalty_button.on('click', () => {
- // this.loyalty_button.show();
- // })
-
- this.toggle_taxes_and_totals(false);
- this.$grand_total.on('click', () => {
- this.toggle_taxes_and_totals();
- });
- }
-
- reset() {
- this.$cart_items.find('.list-item').remove();
- this.$empty_state.show();
- this.$taxes_and_totals.html(this.get_taxes_and_totals());
- this.numpad && this.numpad.reset_value();
- this.customer_field.set_value("");
- this.frm.msgbox = "";
-
- let total_item_qty = 0.0;
- this.frm.set_value("pos_total_qty",total_item_qty);
-
- this.$discount_amount.find('input:text').val('');
- this.wrapper.find('.grand-total-value').text(
- format_currency(this.frm.doc.grand_total, this.frm.currency));
- this.wrapper.find('.rounded-total-value').text(
- format_currency(this.frm.doc.rounded_total, this.frm.currency));
- this.$qty_total.find(".quantity-total").text(total_item_qty);
-
- const customer = this.frm.doc.customer;
- this.customer_field.set_value(customer);
-
- if (this.numpad) {
- const disable_btns = this.disable_numpad_control()
- const enable_btns = [__('Rate'), __('Disc')]
-
- if (disable_btns) {
- enable_btns.filter(btn => !disable_btns.includes(btn))
- }
-
- this.numpad.enable_buttons(enable_btns);
- }
- }
-
- reset_pos_field_value() {
- let value = '';
- if (this.custom_pos_fields) {
- this.custom_pos_fields.forEach(r => {
- value = this.frm.doc[r.fieldname] || r.default_value || '';
-
- if (this.fields) {
- this.fields[r.fieldname].set_value(value);
- }
- })
- }
-
- this.wrapper.find('.pos-fields').toggle(false);
- this.wrapper.find('.pos-fields-octicon').toggle(true);
- }
-
- get_grand_total() {
- let total = this.get_total_template('Grand Total', 'grand-total-value');
-
- if (!cint(frappe.sys_defaults.disable_rounded_total)) {
- total += this.get_total_template('Rounded Total', 'rounded-total-value');
- }
-
- return total;
- }
-
- get_item_qty_total() {
- let total = this.get_total_template('Total Qty', 'quantity-total');
- return total;
- }
-
- get_total_template(label, class_name) {
- return `
- <div class="list-item">
- <div class="list-item__content text-muted">${__(label)}</div>
- <div class="list-item__content list-item__content--flex-2 ${class_name}">0.00</div>
- </div>
- `;
- }
-
- get_discount_amount() {
- const get_currency_symbol = window.get_currency_symbol;
-
- return `
- <div class="list-item">
- <div class="list-item__content list-item__content--flex-2 text-muted">${__('Discount')}</div>
- <div class="list-item__content discount-inputs">
- <input type="text"
- class="form-control additional_discount_percentage text-right"
- placeholder="% 0.00"
- >
- <input type="text"
- class="form-control discount_amount text-right"
- placeholder="${get_currency_symbol(this.frm.doc.currency)} 0.00"
- >
- </div>
- </div>
- `;
- }
-
- get_taxes_and_totals() {
- return `
- <div class="list-item">
- <div class="list-item__content list-item__content--flex-2 text-muted">${__('Net Total')}</div>
- <div class="list-item__content net-total">0.00</div>
- </div>
- <div class="list-item">
- <div class="list-item__content list-item__content--flex-2 text-muted">${__('Taxes')}</div>
- <div class="list-item__content taxes">0.00</div>
- </div>
- `;
- }
-
- toggle_taxes_and_totals(flag) {
- if (flag !== undefined) {
- this.tax_area_is_shown = flag;
- } else {
- this.tax_area_is_shown = !this.tax_area_is_shown;
- }
-
- this.$taxes_and_totals.toggle(this.tax_area_is_shown);
- this.$discount_amount.toggle(this.tax_area_is_shown);
- }
-
- update_taxes_and_totals() {
- if (!this.frm.doc.taxes) { return; }
-
- const currency = this.frm.doc.currency;
- this.frm.refresh_field('taxes');
-
- // Update totals
- this.$taxes_and_totals.find('.net-total')
- .html(format_currency(this.frm.doc.total, currency));
-
- // Update taxes
- const taxes_html = this.frm.doc.taxes.map(tax => {
- return `
- <div>
- <span>${tax.description}</span>
- <span class="text-right bold">
- ${format_currency(tax.tax_amount, currency)}
- </span>
- </div>
- `;
- }).join("");
- this.$taxes_and_totals.find('.taxes').html(taxes_html);
- }
-
- update_grand_total() {
- this.$grand_total.find('.grand-total-value').text(
- format_currency(this.frm.doc.grand_total, this.frm.currency)
- );
-
- this.$grand_total.find('.rounded-total-value').text(
- format_currency(this.frm.doc.rounded_total, this.frm.currency)
- );
- }
-
- update_qty_total() {
- var total_item_qty = 0;
- $.each(this.frm.doc["items"] || [], function (i, d) {
- if (d.qty > 0) {
- total_item_qty += d.qty;
- }
- });
- this.$qty_total.find('.quantity-total').text(total_item_qty);
- this.frm.set_value("pos_total_qty",total_item_qty);
- }
-
- make_customer_field() {
- this.customer_field = frappe.ui.form.make_control({
- df: {
- fieldtype: 'Link',
- label: 'Customer',
- fieldname: 'customer',
- options: 'Customer',
- reqd: 1,
- get_query: function() {
- return {
- query: 'erpnext.controllers.queries.customer_query'
- }
- },
- onchange: () => {
- this.events.on_customer_change(this.customer_field.get_value());
- this.events.get_loyalty_details();
- }
- },
- parent: this.wrapper.find('.customer-field'),
- render_input: true
- });
-
- this.customer_field.set_value(this.frm.doc.customer);
- }
-
- make_pos_fields() {
- const me = this;
-
- this.fields = {};
- this.wrapper.find('.pos-fields-octicon, .more-fields-section').click(() => {
- this.wrapper.find('.pos-fields').toggle();
- this.wrapper.find('.pos-fields-octicon').toggleClass('octicon-chevron-down').toggleClass('octicon-chevron-up');
- });
- this.wrapper.find('.pos-fields').toggle(false);
-
- return new Promise(res => {
- frappe.call({
- method: "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_fields",
- freeze: true,
- }).then(r => {
- if(r.message.length) {
- this.wrapper.find('.pos-field-section').css('display','block');
- this.custom_pos_fields = r.message;
- if (r.message.length < 3) {
- this.wrapper.find('.pos-fields').toggle(true);
- this.wrapper.find('.pos-fields-octicon').toggleClass('octicon-chevron-down').toggleClass('octicon-chevron-up');
- }
-
- r.message.forEach(field => {
- this.fields[field.fieldname] = frappe.ui.form.make_control({
- df: {
- fieldtype: field.fieldtype,
- label: field.label,
- fieldname: field.fieldname,
- options: field.options,
- reqd: field.reqd || 0,
- read_only: field.read_only || 0,
- default: field.default_value,
- onchange: function() {
- if (this.value) {
- me.frm.set_value(this.df.fieldname, this.value);
- }
- },
- get_query: () => {
- return this.get_query_for_pos_fields(field.fieldname)
- },
- },
- parent: this.wrapper.find('.pos-fields'),
- render_input: true
- });
-
- if (this.frm.doc[field.fieldname]) {
- this.fields[field.fieldname].set_value(this.frm.doc[field.fieldname]);
- }
- });
- }
- });
- });
- }
-
- get_query_for_pos_fields(field) {
- if (this.frm.fields_dict && this.frm.fields_dict[field]
- && this.frm.fields_dict[field].get_query) {
- return this.frm.fields_dict[field].get_query(this.frm.doc);
- }
- }
-
- make_loyalty_points() {
- this.available_loyalty_points = frappe.ui.form.make_control({
- df: {
- fieldtype: 'Int',
- label: 'Available Loyalty Points',
- read_only: 1,
- fieldname: 'available_loyalty_points'
- },
- parent: this.wrapper.find('.loyalty-program-field')
- });
- this.available_loyalty_points.set_value(this.frm.doc.loyalty_points);
- }
-
-
- disable_numpad_control() {
- let disabled_btns = [];
- if(!this.frm.allow_edit_rate) {
- disabled_btns.push(__('Rate'));
- }
- if(!this.frm.allow_edit_discount) {
- disabled_btns.push(__('Disc'));
- }
- return disabled_btns;
- }
-
-
- make_numpad() {
-
- var pay_class = {}
- pay_class[__('Pay')]='brand-primary'
- this.numpad = new NumberPad({
- button_array: [
- [1, 2, 3, Qty],
- [4, 5, 6, Disc],
- [7, 8, 9, Rate],
- [Del, 0, '.', Pay]
- ],
- add_class: pay_class,
- disable_highlight: [Qty, Disc, Rate, Pay],
- reset_btns: [Qty, Disc, Rate, Pay],
- del_btn: Del,
- disable_btns: this.disable_numpad_control(),
- wrapper: this.wrapper.find('.number-pad-container'),
- onclick: (btn_value) => {
- // on click
-
- if (!this.selected_item && btn_value !== Pay) {
- frappe.show_alert({
- indicator: 'red',
- message: __('Please select an item in the cart')
- });
- return;
- }
- if ([Qty, Disc, Rate].includes(btn_value)) {
- this.set_input_active(btn_value);
- } else if (btn_value !== Pay) {
- if (!this.selected_item.active_field) {
- frappe.show_alert({
- indicator: 'red',
- message: __('Please select a field to edit from numpad')
- });
- return;
- }
-
- if (this.selected_item.active_field == 'discount_percentage' && this.numpad.get_value() > cint(100)) {
- frappe.show_alert({
- indicator: 'red',
- message: __('Discount amount cannot be greater than 100%')
- });
- this.numpad.reset_value();
- } else {
- const item_code = unescape(this.selected_item.attr('data-item-code'));
- const batch_no = this.selected_item.attr('data-batch-no');
- const field = this.selected_item.active_field;
- const value = this.numpad.get_value();
-
- this.events.on_field_change(item_code, field, value, batch_no);
- }
- }
-
- this.events.on_numpad(btn_value);
- }
- });
- }
-
- set_input_active(btn_value) {
- this.selected_item.removeClass('qty disc rate');
-
- this.numpad.set_active(btn_value);
- if (btn_value === Qty) {
- this.selected_item.addClass('qty');
- this.selected_item.active_field = 'qty';
- } else if (btn_value == Disc) {
- this.selected_item.addClass('disc');
- this.selected_item.active_field = 'discount_percentage';
- } else if (btn_value == Rate) {
- this.selected_item.addClass('rate');
- this.selected_item.active_field = 'rate';
- }
- }
-
- add_item(item) {
- this.$empty_state.hide();
-
- if (this.exists(item.item_code, item.batch_no)) {
- // update quantity
- this.update_item(item);
- } else if (flt(item.qty) > 0.0) {
- // add to cart
- const $item = $(this.get_item_html(item));
- $item.appendTo(this.$cart_items);
- }
- this.highlight_item(item.item_code);
- }
-
- update_item(item) {
- const item_selector = item.batch_no ?
- `[data-batch-no="${item.batch_no}"]` : `[data-item-code="${escape(item.item_code)}"]`;
-
- const $item = this.$cart_items.find(item_selector);
-
- if(item.qty > 0) {
- const is_stock_item = this.get_item_details(item.item_code).is_stock_item;
- const indicator_class = (!is_stock_item || item.actual_qty >= item.qty) ? 'green' : 'red';
- const remove_class = indicator_class == 'green' ? 'red' : 'green';
-
- $item.find('.quantity input').val(item.qty);
- $item.find('.discount').text(item.discount_percentage + '%');
- $item.find('.rate').text(format_currency(item.rate, this.frm.doc.currency));
- $item.addClass(indicator_class);
- $item.removeClass(remove_class);
- } else {
- $item.remove();
- }
- }
-
- get_item_html(item) {
- const is_stock_item = this.get_item_details(item.item_code).is_stock_item;
- const rate = format_currency(item.rate, this.frm.doc.currency);
- const indicator_class = (!is_stock_item || item.actual_qty >= item.qty) ? 'green' : 'red';
- const batch_no = item.batch_no || '';
-
- return `
- <div class="list-item indicator ${indicator_class}" data-item-code="${escape(item.item_code)}"
- data-batch-no="${batch_no}" title="Item: ${item.item_name} Available Qty: ${item.actual_qty} ${item.stock_uom}">
- <div class="item-name list-item__content list-item__content--flex-1.5 ellipsis">
- ${item.item_name}
- </div>
- <div class="quantity list-item__content text-right">
- ${get_quantity_html(item.qty)}
- </div>
- <div class="discount list-item__content text-right">
- ${item.discount_percentage}%
- </div>
- <div class="rate list-item__content text-right">
- ${rate}
- </div>
- </div>
- `;
-
- function get_quantity_html(value) {
- return `
- <div class="input-group input-group-xs">
- <span class="input-group-btn">
- <button class="btn btn-default btn-xs" data-action="increment">+</button>
- </span>
-
- <input class="form-control" type="number" value="${value}">
-
- <span class="input-group-btn">
- <button class="btn btn-default btn-xs" data-action="decrement">-</button>
- </span>
- </div>
- `;
- }
- }
-
- get_item_details(item_code) {
- if (!this.item_data[item_code]) {
- this.item_data[item_code] = this.events.get_item_details(item_code);
- }
-
- return this.item_data[item_code];
- }
-
- exists(item_code, batch_no) {
- const is_exists = batch_no ?
- `[data-batch-no="${batch_no}"]` : `[data-item-code="${escape(item_code)}"]`;
-
- let $item = this.$cart_items.find(is_exists);
-
- return $item.length > 0;
- }
-
- highlight_item(item_code) {
- const $item = this.$cart_items.find(`[data-item-code="${escape(item_code)}"]`);
- $item.addClass('highlight');
- setTimeout(() => $item.removeClass('highlight'), 1000);
- }
-
- scroll_to_item(item_code) {
- const $item = this.$cart_items.find(`[data-item-code="${escape(item_code)}"]`);
- if ($item.length === 0) return;
- const scrollTop = $item.offset().top - this.$cart_items.offset().top + this.$cart_items.scrollTop();
- this.$cart_items.animate({ scrollTop });
- }
-
- bind_events() {
- const me = this;
- const events = this.events;
-
- // quantity change
- this.$cart_items.on('click',
- '[data-action="increment"], [data-action="decrement"]', function() {
- const $btn = $(this);
- const $item = $btn.closest('.list-item[data-item-code]');
- const item_code = unescape($item.attr('data-item-code'));
- const action = $btn.attr('data-action');
-
- if(action === 'increment') {
- events.on_field_change(item_code, 'qty', '+1');
- } else if(action === 'decrement') {
- events.on_field_change(item_code, 'qty', '-1');
- }
- });
-
- this.$cart_items.on('change', '.quantity input', function() {
- const $input = $(this);
- const $item = $input.closest('.list-item[data-item-code]');
- const item_code = unescape($item.attr('data-item-code'));
- events.on_field_change(item_code, 'qty', flt($input.val()));
- });
-
- // current item
- this.$cart_items.on('click', '.list-item', function() {
- me.set_selected_item($(this));
- });
-
- this.wrapper.find('.additional_discount_percentage').on('change', (e) => {
- const discount_percentage = flt(e.target.value,
- precision("additional_discount_percentage"));
-
- frappe.model.set_value(this.frm.doctype, this.frm.docname,
- 'additional_discount_percentage', discount_percentage)
- .then(() => {
- let discount_wrapper = this.wrapper.find('.discount_amount');
- discount_wrapper.val(flt(this.frm.doc.discount_amount,
- precision('discount_amount')));
- discount_wrapper.trigger('change');
- });
- });
-
- this.wrapper.find('.discount_amount').on('change', (e) => {
- const discount_amount = flt(e.target.value, precision('discount_amount'));
- frappe.model.set_value(this.frm.doctype, this.frm.docname,
- 'discount_amount', discount_amount);
- this.frm.trigger('discount_amount')
- .then(() => {
- this.update_discount_fields();
- this.update_taxes_and_totals();
- this.update_grand_total();
- });
- });
- }
-
- update_discount_fields() {
- let discount_wrapper = this.wrapper.find('.additional_discount_percentage');
- let discount_amt_wrapper = this.wrapper.find('.discount_amount');
- discount_wrapper.val(flt(this.frm.doc.additional_discount_percentage,
- precision('additional_discount_percentage')));
- discount_amt_wrapper.val(flt(this.frm.doc.discount_amount,
- precision('discount_amount')));
- }
-
- set_selected_item($item) {
- this.selected_item = $item;
- this.$cart_items.find('.list-item').removeClass('current-item qty disc rate');
- this.selected_item.addClass('current-item');
- this.events.on_select_change();
- }
-
- unselect_all() {
- this.$cart_items.find('.list-item').removeClass('current-item qty disc rate');
- this.selected_item = null;
- this.events.on_select_change();
- }
-}
-
-class POSItems {
- constructor({wrapper, frm, events}) {
- this.wrapper = wrapper;
- this.frm = frm;
- this.items = {};
- this.events = events;
- this.currency = this.frm.doc.currency;
-
- frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name", (r) => {
- this.parent_item_group = r.name;
- this.make_dom();
- this.make_fields();
-
- this.init_clusterize();
- this.bind_events();
- this.load_items_data();
- })
- }
-
- load_items_data() {
- // bootstrap with 20 items
- this.get_items()
- .then(({ items }) => {
- this.all_items = items;
- this.items = items;
- this.render_items(items);
- });
- }
-
- reset_items() {
- this.wrapper.find('.pos-items').empty();
- this.init_clusterize();
- this.load_items_data();
- }
-
- make_dom() {
- this.wrapper.html(`
- <div class="fields">
- <div class="search-field">
- </div>
- <div class="item-group-field">
- </div>
- </div>
- <div class="items-wrapper">
- </div>
- `);
-
- this.items_wrapper = this.wrapper.find('.items-wrapper');
- this.items_wrapper.append(`
- <div class="list-item-table pos-items-wrapper">
- <div class="pos-items image-view-container">
- </div>
- </div>
- `);
- }
-
- make_fields() {
- // Search field
- const me = this;
- this.search_field = frappe.ui.form.make_control({
- df: {
- fieldtype: 'Data',
- label: __('Search Item (Ctrl + i)'),
- placeholder: __('Search by item code, serial number, batch no or barcode')
- },
- parent: this.wrapper.find('.search-field'),
- render_input: true,
- });
-
- frappe.ui.keys.on('ctrl+i', () => {
- this.search_field.set_focus();
- });
-
- this.search_field.$input.on('input', (e) => {
- clearTimeout(this.last_search);
- this.last_search = setTimeout(() => {
- const search_term = e.target.value;
- const item_group = this.item_group_field ?
- this.item_group_field.get_value() : '';
-
- this.filter_items({ search_term:search_term, item_group: item_group});
- }, 300);
- });
-
- this.item_group_field = frappe.ui.form.make_control({
- df: {
- fieldtype: 'Link',
- label: 'Item Group',
- options: 'Item Group',
- default: me.parent_item_group,
- onchange: () => {
- const item_group = this.item_group_field.get_value();
- if (item_group) {
- this.filter_items({ item_group: item_group });
- }
- },
- get_query: () => {
- return {
- query: 'erpnext.selling.page.point_of_sale.point_of_sale.item_group_query',
- filters: {
- pos_profile: this.frm.doc.pos_profile
- }
- };
- }
- },
- parent: this.wrapper.find('.item-group-field'),
- render_input: true
- });
- }
-
- init_clusterize() {
- this.clusterize = new Clusterize({
- scrollElem: this.wrapper.find('.pos-items-wrapper')[0],
- contentElem: this.wrapper.find('.pos-items')[0],
- rows_in_block: 6
- });
- }
-
- render_items(items) {
- let _items = items || this.items;
-
- const all_items = Object.values(_items).map(item => this.get_item_html(item));
- let row_items = [];
-
- const row_container = '<div class="image-view-row">';
- let curr_row = row_container;
-
- for (let i=0; i < all_items.length; i++) {
- // wrap 4 items in a div to emulate
- // a row for clusterize
- if(i % 4 === 0 && i !== 0) {
- curr_row += '</div>';
- row_items.push(curr_row);
- curr_row = row_container;
- }
- curr_row += all_items[i];
-
- if(i == all_items.length - 1) {
- row_items.push(curr_row);
- }
- }
-
- this.clusterize.update(row_items);
- }
-
- filter_items({ search_term='', item_group=this.parent_item_group }={}) {
- if (search_term) {
- search_term = search_term.toLowerCase();
-
- // memoize
- this.search_index = this.search_index || {};
- if (this.search_index[search_term]) {
- const items = this.search_index[search_term];
- this.items = items;
- this.render_items(items);
- this.set_item_in_the_cart(items);
- return;
- }
- } else if (item_group == this.parent_item_group) {
- this.items = this.all_items;
- return this.render_items(this.all_items);
- }
-
- this.get_items({search_value: search_term, item_group })
- .then(({ items, serial_no, batch_no, barcode }) => {
- if (search_term && !barcode) {
- this.search_index[search_term] = items;
- }
-
- this.items = items;
- this.render_items(items);
- this.set_item_in_the_cart(items, serial_no, batch_no, barcode);
- });
- }
-
- set_item_in_the_cart(items, serial_no, batch_no, barcode) {
- if (serial_no) {
- this.events.update_cart(items[0].item_code,
- 'serial_no', serial_no);
- this.reset_search_field();
- return;
- }
-
- if (batch_no) {
- this.events.update_cart(items[0].item_code,
- 'batch_no', batch_no);
- this.reset_search_field();
- return;
- }
-
- if (items.length === 1 && (serial_no || batch_no || barcode)) {
- this.events.update_cart(items[0].item_code,
- 'qty', '+1');
- this.reset_search_field();
- }
- }
-
- reset_search_field() {
- this.search_field.set_value('');
- this.search_field.$input.trigger("input");
- }
-
- bind_events() {
- var me = this;
- this.wrapper.on('click', '.pos-item-wrapper', function() {
- const $item = $(this);
- const item_code = unescape($item.attr('data-item-code'));
- me.events.update_cart(item_code, 'qty', '+1');
- });
- }
-
- get(item_code) {
- let item = {};
- this.items.map(data => {
- if (data.item_code === item_code) {
- item = data;
- }
- })
-
- return item
- }
-
- get_all() {
- return this.items;
- }
-
- get_item_html(item) {
- const price_list_rate = format_currency(item.price_list_rate, this.currency);
- const { item_code, item_name, item_image} = item;
- const item_title = item_name || item_code;
-
- const template = `
- <div class="pos-item-wrapper image-view-item" data-item-code="${escape(item_code)}">
- <div class="image-view-header">
- <div>
- <a class="grey list-id" data-name="${item_code}" title="${item_title}">
- ${item_title}
- </a>
- </div>
- </div>
- <div class="image-view-body">
- <a data-item-code="${item_code}"
- title="${item_title}"
- >
- <div class="image-field"
- style="${!item_image ? 'background-color: #fafbfc;' : ''} border: 0px;"
- >
- ${!item_image ? `<span class="placeholder-text">
- ${frappe.get_abbr(item_title)}
- </span>` : '' }
- ${item_image ? `<img src="${item_image}" alt="${item_title}">` : '' }
- </div>
- <span class="price-info">
- ${price_list_rate}
- </span>
- </a>
- </div>
- </div>
- `;
-
- return template;
- }
-
- get_items({start = 0, page_length = 40, search_value='', item_group=this.parent_item_group}={}) {
- const price_list = this.frm.doc.selling_price_list;
- return new Promise(res => {
- frappe.call({
- method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items",
- freeze: true,
- args: {
- start,
- page_length,
- price_list,
- item_group,
- search_value,
- pos_profile: this.frm.doc.pos_profile
- }
- }).then(r => {
- // const { items, serial_no, batch_no } = r.message;
-
- // this.serial_no = serial_no || "";
- res(r.message);
- });
- });
- }
-}
-
-class NumberPad {
- constructor({
- wrapper, onclick, button_array,
- add_class={}, disable_highlight=[],
- reset_btns=[], del_btn='', disable_btns
- }) {
- this.wrapper = wrapper;
- this.onclick = onclick;
- this.button_array = button_array;
- this.add_class = add_class;
- this.disable_highlight = disable_highlight;
- this.reset_btns = reset_btns;
- this.del_btn = del_btn;
- this.disable_btns = disable_btns || [];
- this.make_dom();
- this.bind_events();
- this.value = '';
- }
-
- make_dom() {
- if (!this.button_array) {
- this.button_array = [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
- ['', 0, '']
- ];
- }
-
- this.wrapper.html(`
- <div class="number-pad">
- ${this.button_array.map(get_row).join("")}
- </div>
- `);
-
- function get_row(row) {
- return '<div class="num-row">' + row.map(get_col).join("") + '</div>';
- }
-
- function get_col(col) {
- return `<div class="num-col" data-value="${col}"><div>${col}</div></div>`;
- }
-
- this.set_class();
-
- if(this.disable_btns) {
- this.disable_btns.forEach((btn) => {
- const $btn = this.get_btn(btn);
- $btn.prop("disabled", true)
- $btn.hover(() => {
- $btn.css('cursor','not-allowed');
- })
- })
- }
- }
-
- enable_buttons(btns) {
- btns.forEach((btn) => {
- const $btn = this.get_btn(btn);
- $btn.prop("disabled", false)
- $btn.hover(() => {
- $btn.css('cursor','pointer');
- })
- })
- }
-
- set_class() {
- for (const btn in this.add_class) {
- const class_name = this.add_class[btn];
- this.get_btn(btn).addClass(class_name);
- }
- }
-
- bind_events() {
- // bind click event
- const me = this;
- this.wrapper.on('click', '.num-col', function() {
- const $btn = $(this);
- const btn_value = $btn.attr('data-value');
- if (!me.disable_highlight.includes(btn_value)) {
- me.highlight_button($btn);
- }
- if (me.reset_btns.includes(btn_value)) {
- me.reset_value();
- } else {
- if (btn_value === me.del_btn) {
- me.value = me.value.substr(0, me.value.length - 1);
- } else {
- me.value += btn_value;
- }
- }
- me.onclick(btn_value);
- });
- }
-
- reset_value() {
- this.value = '';
- }
-
- get_value() {
- return flt(this.value);
- }
-
- get_btn(btn_value) {
- return this.wrapper.find(`.num-col[data-value="${btn_value}"]`);
- }
-
- highlight_button($btn) {
- $btn.addClass('highlight');
- setTimeout(() => $btn.removeClass('highlight'), 1000);
- }
-
- set_active(btn_value) {
- const $btn = this.get_btn(btn_value);
- this.wrapper.find('.num-col').removeClass('active');
- $btn.addClass('active');
- }
-
- set_inactive() {
- this.wrapper.find('.num-col').removeClass('active');
- }
-}
-
-class Payment {
- constructor({frm, events}) {
- this.frm = frm;
- this.events = events;
- this.make();
- this.bind_events();
- this.set_primary_action();
- }
-
- open_modal() {
- this.dialog.show();
- }
-
- make() {
- this.set_flag();
- this.dialog = new frappe.ui.Dialog({
- fields: this.get_fields(),
- width: 800,
- invoice_frm: this.frm
- });
-
- this.set_title();
-
- this.$body = this.dialog.body;
-
- this.numpad = new NumberPad({
- wrapper: $(this.$body).find('[data-fieldname="numpad"]'),
- button_array: [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
- [__('Del'), 0, '.'],
- ],
- onclick: () => {
- if(this.fieldname) {
- this.dialog.set_value(this.fieldname, this.numpad.get_value());
- }
- }
- });
- }
-
- set_title() {
- let title = __('Total Amount {0}',
- [format_currency(this.frm.doc.rounded_total || this.frm.doc.grand_total,
- this.frm.doc.currency)]);
-
- this.dialog.set_title(title);
- }
-
- bind_events() {
- var me = this;
- $(this.dialog.body).find('.input-with-feedback').focusin(function() {
- me.numpad.reset_value();
- me.fieldname = $(this).prop('dataset').fieldname;
- if (me.frm.doc.outstanding_amount > 0 &&
- !in_list(['write_off_amount', 'change_amount'], me.fieldname)) {
- me.frm.doc.payments.forEach((data) => {
- if (data.mode_of_payment == me.fieldname && !data.amount) {
- me.dialog.set_value(me.fieldname,
- me.frm.doc.outstanding_amount / me.frm.doc.conversion_rate);
- return;
- }
- })
- }
- });
- }
-
- set_primary_action() {
- var me = this;
-
- this.dialog.set_primary_action(__("Submit"), function() {
- me.dialog.hide();
- me.events.submit_form();
- });
- }
-
- get_fields() {
- const me = this;
-
- let fields = this.frm.doc.payments.map(p => {
- return {
- fieldtype: 'Currency',
- label: __(p.mode_of_payment),
- options: me.frm.doc.currency,
- fieldname: p.mode_of_payment,
- default: p.amount,
- onchange: () => {
- const value = this.dialog.get_value(this.fieldname) || 0;
- me.update_payment_value(this.fieldname, value);
- }
- };
- });
-
- fields = fields.concat([
- {
- fieldtype: 'Column Break',
- },
- {
- fieldtype: 'HTML',
- fieldname: 'numpad'
- },
- {
- fieldtype: 'Section Break',
- depends_on: 'eval: this.invoice_frm.doc.loyalty_program'
- },
- {
- fieldtype: 'Check',
- label: 'Redeem Loyalty Points',
- fieldname: 'redeem_loyalty_points',
- onchange: () => {
- me.update_cur_frm_value("redeem_loyalty_points", () => {
- frappe.flags.redeem_loyalty_points = false;
- me.update_loyalty_points();
- });
- }
- },
- {
- fieldtype: 'Column Break',
- },
- {
- fieldtype: 'Int',
- fieldname: "loyalty_points",
- label: __("Loyalty Points"),
- depends_on: "redeem_loyalty_points",
- onchange: () => {
- me.update_cur_frm_value("loyalty_points", () => {
- frappe.flags.loyalty_points = false;
- me.update_loyalty_points();
- });
- }
- },
- {
- fieldtype: 'Currency',
- label: __("Loyalty Amount"),
- fieldname: "loyalty_amount",
- options: me.frm.doc.currency,
- read_only: 1,
- depends_on: "redeem_loyalty_points"
- },
- {
- fieldtype: 'Section Break',
- },
- {
- fieldtype: 'Currency',
- label: __("Write off Amount"),
- options: me.frm.doc.currency,
- fieldname: "write_off_amount",
- default: me.frm.doc.write_off_amount,
- onchange: () => {
- me.update_cur_frm_value('write_off_amount', () => {
- frappe.flags.change_amount = false;
- me.update_change_amount();
- });
- }
- },
- {
- fieldtype: 'Column Break',
- },
- {
- fieldtype: 'Currency',
- label: __("Change Amount"),
- options: me.frm.doc.currency,
- fieldname: "change_amount",
- default: me.frm.doc.change_amount,
- onchange: () => {
- me.update_cur_frm_value('change_amount', () => {
- frappe.flags.write_off_amount = false;
- me.update_write_off_amount();
- });
- }
- },
- {
- fieldtype: 'Section Break',
- },
- {
- fieldtype: 'Currency',
- label: __("Paid Amount"),
- options: me.frm.doc.currency,
- fieldname: "paid_amount",
- default: me.frm.doc.paid_amount,
- read_only: 1
- },
- {
- fieldtype: 'Column Break',
- },
- {
- fieldtype: 'Currency',
- label: __("Outstanding Amount"),
- options: me.frm.doc.currency,
- fieldname: "outstanding_amount",
- default: me.frm.doc.outstanding_amount,
- read_only: 1
- },
- ]);
-
- return fields;
- }
-
- set_flag() {
- frappe.flags.write_off_amount = true;
- frappe.flags.change_amount = true;
- frappe.flags.loyalty_points = true;
- frappe.flags.redeem_loyalty_points = true;
- frappe.flags.payment_method = true;
- }
-
- update_cur_frm_value(fieldname, callback) {
- if (frappe.flags[fieldname]) {
- const value = this.dialog.get_value(fieldname);
- this.frm.set_value(fieldname, value)
- .then(() => {
- callback();
- });
- }
-
- frappe.flags[fieldname] = true;
- }
-
- update_payment_value(fieldname, value) {
- var me = this;
- $.each(this.frm.doc.payments, function(i, data) {
- if (__(data.mode_of_payment) == __(fieldname)) {
- frappe.model.set_value('Sales Invoice Payment', data.name, 'amount', value)
- .then(() => {
- me.update_change_amount();
- me.update_write_off_amount();
- });
- }
- });
- }
-
- update_change_amount() {
- this.dialog.set_value("change_amount", this.frm.doc.change_amount);
- this.show_paid_amount();
- }
-
- update_write_off_amount() {
- this.dialog.set_value("write_off_amount", this.frm.doc.write_off_amount);
- }
-
- show_paid_amount() {
- this.dialog.set_value("paid_amount", this.frm.doc.paid_amount);
- this.dialog.set_value("outstanding_amount", this.frm.doc.outstanding_amount);
- }
-
- update_payment_amount() {
- var me = this;
- $.each(this.frm.doc.payments, function(i, data) {
- console.log("setting the ", data.mode_of_payment, " for the value", data.amount);
- me.dialog.set_value(data.mode_of_payment, data.amount);
- });
- }
-
- update_loyalty_points() {
- if (this.dialog.get_value("redeem_loyalty_points")) {
- this.dialog.set_value("loyalty_points", this.frm.doc.loyalty_points);
- this.dialog.set_value("loyalty_amount", this.frm.doc.loyalty_amount);
- this.update_payment_amount();
- this.show_paid_amount();
- }
- }
-
-}
+ // online
+ wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
+ window.cur_pos = wrapper.pos;
+};
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.json b/erpnext/selling/page/point_of_sale/point_of_sale.json
index 6d2f5f2..99b86e4 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.json
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.json
@@ -1,33 +1,33 @@
{
- "content": null,
- "creation": "2017-08-07 17:08:56.737947",
- "docstatus": 0,
- "doctype": "Page",
- "idx": 0,
- "modified": "2017-09-11 13:49:05.415211",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "point-of-sale",
- "owner": "Administrator",
- "page_name": "Point of Sale",
- "restrict_to_domain": "Retail",
+ "content": null,
+ "creation": "2020-01-28 22:05:44.819140",
+ "docstatus": 0,
+ "doctype": "Page",
+ "idx": 0,
+ "modified": "2020-06-01 15:41:06.348380",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "point-of-sale",
+ "owner": "Administrator",
+ "page_name": "Point of Sale",
+ "restrict_to_domain": "Retail",
"roles": [
{
"role": "Accounts User"
- },
+ },
{
"role": "Accounts Manager"
- },
+ },
{
"role": "Sales User"
- },
+ },
{
"role": "Sales Manager"
}
- ],
- "script": null,
- "standard": "Yes",
- "style": null,
- "system_page": 0,
- "title": "Point of Sale"
+ ],
+ "script": null,
+ "standard": "Yes",
+ "style": null,
+ "system_page": 0,
+ "title": "Point Of Sale"
}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index dfa0f7f..9f8410f 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -6,6 +6,7 @@
from frappe.utils.nestedset import get_root_of
from frappe.utils import cint
from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups
+from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability
from six import string_types
@@ -43,6 +44,7 @@
SELECT
name AS item_code,
item_name,
+ description,
stock_uom,
image AS item_image,
idx AS idx,
@@ -53,10 +55,11 @@
disabled = 0
AND has_variants = 0
AND is_sales_item = 1
+ AND is_fixed_asset = 0
AND item_group in (SELECT name FROM `tabItem Group` WHERE lft >= {lft} AND rgt <= {rgt})
AND {condition}
ORDER BY
- idx desc
+ name asc
LIMIT
{start}, {page_length}"""
.format(
@@ -73,32 +76,14 @@
fields = ["item_code", "price_list_rate", "currency"],
filters = {'price_list': price_list, 'item_code': ['in', items]})
- item_prices, bin_data = {}, {}
+ item_prices = {}
for d in item_prices_data:
item_prices[d.item_code] = d
- # prepare filter for bin query
- bin_filters = {'item_code': ['in', items]}
- if warehouse:
- bin_filters['warehouse'] = warehouse
- if display_items_in_stock:
- bin_filters['actual_qty'] = [">", 0]
-
- # query item bin
- bin_data = frappe.get_all(
- 'Bin', fields=['item_code', 'sum(actual_qty) as actual_qty'],
- filters=bin_filters, group_by='item_code'
- )
-
- # convert list of dict into dict as {item_code: actual_qty}
- bin_dict = {}
- for b in bin_data:
- bin_dict[b.get('item_code')] = b.get('actual_qty')
-
for item in items_data:
item_code = item.item_code
item_price = item_prices.get(item_code) or {}
- item_stock_qty = bin_dict.get(item_code)
+ item_stock_qty = get_stock_availability(item_code, warehouse)
if display_items_in_stock and not item_stock_qty:
pass
@@ -116,6 +101,13 @@
'items': result
}
+ if len(res['items']) == 1:
+ res['items'][0].setdefault('serial_no', serial_no)
+ res['items'][0].setdefault('batch_no', batch_no)
+ res['items'][0].setdefault('barcode', barcode)
+
+ return res
+
if serial_no:
res.update({
'serial_no': serial_no
@@ -167,6 +159,8 @@
return cond % tuple(item_groups)
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def item_group_query(doctype, txt, searchfield, start, page_len, filters):
item_groups = []
cond = "1=1"
@@ -185,6 +179,73 @@
{'txt': '%%%s%%' % txt})
@frappe.whitelist()
-def get_pos_fields():
- return frappe.get_all("POS Field", fields=["label", "fieldname",
- "fieldtype", "default_value", "reqd", "read_only", "options"])
\ No newline at end of file
+def check_opening_entry(user):
+ open_vouchers = frappe.db.get_all("POS Opening Entry",
+ filters = {
+ "user": user,
+ "pos_closing_entry": ["in", ["", None]],
+ "docstatus": 1
+ },
+ fields = ["name", "company", "pos_profile", "period_start_date"],
+ order_by = "period_start_date desc"
+ )
+
+ return open_vouchers
+
+@frappe.whitelist()
+def create_opening_voucher(pos_profile, company, balance_details):
+ import json
+ balance_details = json.loads(balance_details)
+
+ new_pos_opening = frappe.get_doc({
+ 'doctype': 'POS Opening Entry',
+ "period_start_date": frappe.utils.get_datetime(),
+ "posting_date": frappe.utils.getdate(),
+ "user": frappe.session.user,
+ "pos_profile": pos_profile,
+ "company": company,
+ })
+ new_pos_opening.set("balance_details", balance_details)
+ new_pos_opening.submit()
+
+ return new_pos_opening.as_dict()
+
+@frappe.whitelist()
+def get_past_order_list(search_term, status, limit=20):
+ fields = ['name', 'grand_total', 'currency', 'customer', 'posting_time', 'posting_date']
+ invoice_list = []
+
+ if search_term and status:
+ invoices_by_customer = frappe.db.get_all('POS Invoice', filters={
+ 'customer': ['like', '%{}%'.format(search_term)],
+ 'status': status
+ }, fields=fields)
+ invoices_by_name = frappe.db.get_all('POS Invoice', filters={
+ 'name': ['like', '%{}%'.format(search_term)],
+ 'status': status
+ }, fields=fields)
+
+ invoice_list = invoices_by_customer + invoices_by_name
+ elif status:
+ invoice_list = frappe.db.get_all('POS Invoice', filters={
+ 'status': status
+ }, fields=fields)
+
+ return invoice_list
+
+@frappe.whitelist()
+def set_customer_info(fieldname, customer, value=""):
+ if fieldname == 'loyalty_program':
+ frappe.db.set_value('Customer', customer, 'loyalty_program', value)
+
+ contact = frappe.get_cached_value('Customer', customer, 'customer_primary_contact')
+
+ if contact:
+ contact_doc = frappe.get_doc('Contact', contact)
+ if fieldname == 'email_id':
+ contact_doc.set('email_ids', [{ 'email_id': value, 'is_primary': 1}])
+ frappe.db.set_value('Customer', customer, 'email_id', value)
+ elif fieldname == 'mobile_no':
+ contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
+ frappe.db.set_value('Customer', customer, 'mobile_no', value)
+ contact_doc.save()
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
new file mode 100644
index 0000000..483ef78
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -0,0 +1,714 @@
+{% include "erpnext/selling/page/point_of_sale/onscan.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_item_selector.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_item_cart.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_item_details.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_payment.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_past_order_list.js" %}
+{% include "erpnext/selling/page/point_of_sale/pos_past_order_summary.js" %}
+
+erpnext.PointOfSale.Controller = class {
+ constructor(wrapper) {
+ this.wrapper = $(wrapper).find('.layout-main-section');
+ this.page = wrapper.page;
+
+ this.load_assets();
+ }
+
+ load_assets() {
+ // after loading assets first check if opening entry has been made
+ frappe.require(['assets/erpnext/css/pos.css'], this.check_opening_entry.bind(this));
+ }
+
+ check_opening_entry() {
+ return frappe.call("erpnext.selling.page.point_of_sale.point_of_sale.check_opening_entry", { "user": frappe.session.user })
+ .then((r) => {
+ if (r.message.length) {
+ // assuming only one opening voucher is available for the current user
+ this.prepare_app_defaults(r.message[0]);
+ } else {
+ this.create_opening_voucher();
+ }
+ });
+ }
+
+ create_opening_voucher() {
+ const table_fields = [
+ { fieldname: "mode_of_payment", fieldtype: "Link", in_list_view: 1, label: "Mode of Payment", options: "Mode of Payment", reqd: 1 },
+ { fieldname: "opening_amount", fieldtype: "Currency", in_list_view: 1, label: "Opening Amount", options: "company:company_currency", reqd: 1 }
+ ];
+
+ const dialog = new frappe.ui.Dialog({
+ title: __('Create POS Opening Entry'),
+ fields: [
+ {
+ fieldtype: 'Link', label: __('Company'), default: frappe.defaults.get_default('company'),
+ options: 'Company', fieldname: 'company', reqd: 1
+ },
+ {
+ fieldtype: 'Link', label: __('POS Profile'),
+ options: 'POS Profile', fieldname: 'pos_profile', reqd: 1,
+ onchange: () => {
+ const pos_profile = dialog.fields_dict.pos_profile.get_value();
+ const company = dialog.fields_dict.company.get_value();
+ const user = frappe.session.user
+
+ if (!pos_profile || !company || !user) return;
+
+ // auto fetch last closing entry's balance details
+ frappe.db.get_list("POS Closing Entry", {
+ filters: { company, pos_profile, user },
+ limit: 1,
+ order_by: 'period_end_date desc'
+ }).then((res) => {
+ if (!res.length) return;
+ const pos_closing_entry = res[0];
+ frappe.db.get_doc("POS Closing Entry", pos_closing_entry.name).then(({ payment_reconciliation }) => {
+ dialog.fields_dict.balance_details.df.data = [];
+ payment_reconciliation.forEach(pay => {
+ const { mode_of_payment, closing_amount } = pay;
+ dialog.fields_dict.balance_details.df.data.push({
+ mode_of_payment: mode_of_payment
+ });
+ });
+ dialog.fields_dict.balance_details.grid.refresh();
+ });
+ });
+ }
+ },
+ {
+ fieldname: "balance_details",
+ fieldtype: "Table",
+ label: "Opening Balance Details",
+ cannot_add_rows: false,
+ in_place_edit: true,
+ reqd: 1,
+ data: [],
+ fields: table_fields
+ }
+ ],
+ primary_action: ({ company, pos_profile, balance_details }) => {
+ if (!balance_details.length) {
+ frappe.show_alert({
+ message: __("Please add Mode of payments and opening balance details."),
+ indicator: 'red'
+ })
+ frappe.utils.play_sound("error");
+ return;
+ }
+ frappe.dom.freeze();
+ return frappe.call("erpnext.selling.page.point_of_sale.point_of_sale.create_opening_voucher",
+ { pos_profile, company, balance_details })
+ .then((r) => {
+ frappe.dom.unfreeze();
+ dialog.hide();
+ if (r.message) {
+ this.prepare_app_defaults(r.message);
+ }
+ })
+ },
+ primary_action_label: __('Submit')
+ });
+ dialog.show();
+ }
+
+ prepare_app_defaults(data) {
+ this.pos_opening = data.name;
+ this.company = data.company;
+ this.pos_profile = data.pos_profile;
+ this.pos_opening_time = data.period_start_date;
+
+ frappe.db.get_value('Stock Settings', undefined, 'allow_negative_stock').then(({ message }) => {
+ this.allow_negative_stock = flt(message.allow_negative_stock) || false;
+ });
+
+ frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
+ this.customer_groups = profile.customer_groups.map(group => group.customer_group);
+ this.cart.make_customer_selector();
+ });
+
+ this.item_stock_map = {};
+
+ this.make_app();
+ }
+
+ set_opening_entry_status() {
+ this.page.set_title_sub(
+ `<span class="indicator orange">
+ <a class="text-muted" href="#Form/POS%20Opening%20Entry/${this.pos_opening}">
+ Opened at ${moment(this.pos_opening_time).format("Do MMMM, h:mma")}
+ </a>
+ </span>`);
+ }
+
+ make_app() {
+ return frappe.run_serially([
+ () => frappe.dom.freeze(),
+ () => {
+ this.set_opening_entry_status();
+ this.prepare_dom();
+ this.prepare_components();
+ this.prepare_menu();
+ },
+ () => this.make_new_invoice(),
+ () => frappe.dom.unfreeze(),
+ () => this.page.set_title(__('Point of Sale Beta')),
+ ]);
+ }
+
+ prepare_dom() {
+ this.wrapper.append(`
+ <div class="app grid grid-cols-10 pt-8 gap-6"></div>`
+ );
+
+ this.$components_wrapper = this.wrapper.find('.app');
+ }
+
+ prepare_components() {
+ this.init_item_selector();
+ this.init_item_details();
+ this.init_item_cart();
+ this.init_payments();
+ this.init_recent_order_list();
+ this.init_order_summary();
+ }
+
+ prepare_menu() {
+ var me = this;
+ this.page.clear_menu();
+
+ this.page.add_menu_item(__("Form View"), function () {
+ frappe.model.sync(me.frm.doc);
+ frappe.set_route("Form", me.frm.doc.doctype, me.frm.doc.name);
+ });
+
+ this.page.add_menu_item(__("Toggle Recent Orders"), () => {
+ const show = this.recent_order_list.$component.hasClass('d-none');
+ this.toggle_recent_order_list(show);
+ });
+
+ this.page.add_menu_item(__("Save as Draft"), this.save_draft_invoice.bind(this));
+
+ frappe.ui.keys.on("ctrl+s", this.save_draft_invoice.bind(this));
+
+ this.page.add_menu_item(__('Close the POS'), this.close_pos.bind(this));
+
+ frappe.ui.keys.on("shift+ctrl+s", this.close_pos.bind(this));
+ }
+
+ save_draft_invoice() {
+ if (!this.$components_wrapper.is(":visible")) return;
+
+ if (this.frm.doc.items.length == 0) {
+ frappe.show_alert({
+ message:__("You must add atleast one item to save it as draft."),
+ indicator:'red'
+ });
+ frappe.utils.play_sound("error");
+ return;
+ }
+
+ this.frm.save(undefined, undefined, undefined, () => {
+ frappe.show_alert({
+ message:__("There was an error saving the document."),
+ indicator:'red'
+ });
+ frappe.utils.play_sound("error");
+ }).then(() => {
+ frappe.run_serially([
+ () => frappe.dom.freeze(),
+ () => this.make_new_invoice(),
+ () => frappe.dom.unfreeze(),
+ ]);
+ })
+ }
+
+ close_pos() {
+ if (!this.$components_wrapper.is(":visible")) return;
+
+ let voucher = frappe.model.get_new_doc('POS Closing Entry');
+ voucher.pos_profile = this.frm.doc.pos_profile;
+ voucher.user = frappe.session.user;
+ voucher.company = this.frm.doc.company;
+ voucher.pos_opening_entry = this.pos_opening;
+ voucher.period_end_date = frappe.datetime.now_datetime();
+ voucher.posting_date = frappe.datetime.now_date();
+ frappe.set_route('Form', 'POS Closing Entry', voucher.name);
+ }
+
+ init_item_selector() {
+ this.item_selector = new erpnext.PointOfSale.ItemSelector({
+ wrapper: this.$components_wrapper,
+ pos_profile: this.pos_profile,
+ events: {
+ item_selected: args => this.on_cart_update(args),
+
+ get_frm: () => this.frm || {},
+
+ get_allowed_item_group: () => this.item_groups
+ }
+ })
+ }
+
+ init_item_cart() {
+ this.cart = new erpnext.PointOfSale.ItemCart({
+ wrapper: this.$components_wrapper,
+ events: {
+ get_frm: () => this.frm,
+
+ cart_item_clicked: (item_code, batch_no, uom) => {
+ const item_row = this.frm.doc.items.find(
+ i => i.item_code === item_code
+ && i.uom === uom
+ && (!batch_no || (batch_no && i.batch_no === batch_no))
+ );
+ this.item_details.toggle_item_details_section(item_row);
+ },
+
+ numpad_event: (value, action) => this.update_item_field(value, action),
+
+ checkout: () => this.payment.checkout(),
+
+ edit_cart: () => this.payment.edit_cart(),
+
+ customer_details_updated: (details) => {
+ this.customer_details = details;
+ // will add/remove LP payment method
+ this.payment.render_loyalty_points_payment_mode();
+ },
+
+ get_allowed_customer_group: () => this.customer_groups
+ }
+ })
+ }
+
+ init_item_details() {
+ this.item_details = new erpnext.PointOfSale.ItemDetails({
+ wrapper: this.$components_wrapper,
+ events: {
+ get_frm: () => this.frm,
+
+ toggle_item_selector: (minimize) => {
+ this.item_selector.resize_selector(minimize);
+ this.cart.toggle_numpad(minimize);
+ },
+
+ form_updated: async (cdt, cdn, fieldname, value) => {
+ const item_row = frappe.model.get_doc(cdt, cdn);
+ if (item_row && item_row[fieldname] != value) {
+
+ if (fieldname === 'qty' && flt(value) == 0) {
+ this.remove_item_from_cart();
+ return;
+ }
+
+ const { item_code, batch_no, uom } = this.item_details.current_item;
+ const event = {
+ field: fieldname,
+ value,
+ item: { item_code, batch_no, uom }
+ }
+ return this.on_cart_update(event)
+ }
+ },
+
+ item_field_focused: (fieldname) => {
+ this.cart.toggle_numpad_field_edit(fieldname);
+ },
+ set_value_in_current_cart_item: (selector, value) => {
+ this.cart.update_selector_value_in_cart_item(selector, value, this.item_details.current_item);
+ },
+ clone_new_batch_item_in_frm: (batch_serial_map, current_item) => {
+ // called if serial nos are 'auto_selected' and if those serial nos belongs to multiple batches
+ // for each unique batch new item row is added in the form & cart
+ Object.keys(batch_serial_map).forEach(batch => {
+ const { item_code, batch_no } = current_item;
+ const item_to_clone = this.frm.doc.items.find(i => i.item_code === item_code && i.batch_no === batch_no);
+ const new_row = this.frm.add_child("items", { ...item_to_clone });
+ // update new serialno and batch
+ new_row.batch_no = batch;
+ new_row.serial_no = batch_serial_map[batch].join(`\n`);
+ new_row.qty = batch_serial_map[batch].length;
+ this.frm.doc.items.forEach(row => {
+ if (item_code === row.item_code) {
+ this.update_cart_html(row);
+ }
+ });
+ })
+ },
+ remove_item_from_cart: () => this.remove_item_from_cart(),
+ get_item_stock_map: () => this.item_stock_map,
+ close_item_details: () => {
+ this.item_details.toggle_item_details_section(undefined);
+ this.cart.prev_action = undefined;
+ this.cart.toggle_item_highlight();
+ },
+ get_available_stock: (item_code, warehouse) => this.get_available_stock(item_code, warehouse)
+ }
+ });
+ }
+
+ init_payments() {
+ this.payment = new erpnext.PointOfSale.Payment({
+ wrapper: this.$components_wrapper,
+ events: {
+ get_frm: () => this.frm || {},
+
+ get_customer_details: () => this.customer_details || {},
+
+ toggle_other_sections: (show) => {
+ if (show) {
+ this.item_details.$component.hasClass('d-none') ? '' : this.item_details.$component.addClass('d-none');
+ this.item_selector.$component.addClass('d-none');
+ } else {
+ this.item_selector.$component.removeClass('d-none');
+ }
+ },
+
+ submit_invoice: () => {
+ this.frm.savesubmit()
+ .then((r) => {
+ // this.set_invoice_status();
+ this.toggle_components(false);
+ this.order_summary.toggle_component(true);
+ this.order_summary.load_summary_of(this.frm.doc, true);
+ frappe.show_alert({
+ indicator: 'green',
+ message: __(`POS invoice ${r.doc.name} created succesfully`)
+ });
+ });
+ }
+ }
+ });
+ }
+
+ init_recent_order_list() {
+ this.recent_order_list = new erpnext.PointOfSale.PastOrderList({
+ wrapper: this.$components_wrapper,
+ events: {
+ open_invoice_data: (name) => {
+ frappe.db.get_doc('POS Invoice', name).then((doc) => {
+ this.order_summary.load_summary_of(doc);
+ });
+ },
+ reset_summary: () => this.order_summary.show_summary_placeholder()
+ }
+ })
+ }
+
+ init_order_summary() {
+ this.order_summary = new erpnext.PointOfSale.PastOrderSummary({
+ wrapper: this.$components_wrapper,
+ events: {
+ get_frm: () => this.frm,
+
+ process_return: (name) => {
+ this.recent_order_list.toggle_component(false);
+ frappe.db.get_doc('POS Invoice', name).then((doc) => {
+ frappe.run_serially([
+ () => this.make_return_invoice(doc),
+ () => this.cart.load_invoice(),
+ () => this.item_selector.toggle_component(true)
+ ]);
+ });
+ },
+ edit_order: (name) => {
+ this.recent_order_list.toggle_component(false);
+ frappe.run_serially([
+ () => this.frm.refresh(name),
+ () => this.cart.load_invoice(),
+ () => this.item_selector.toggle_component(true)
+ ]);
+ },
+ new_order: () => {
+ frappe.run_serially([
+ () => frappe.dom.freeze(),
+ () => this.make_new_invoice(),
+ () => this.item_selector.toggle_component(true),
+ () => frappe.dom.unfreeze(),
+ ]);
+ }
+ }
+ })
+ }
+
+
+
+ toggle_recent_order_list(show) {
+ this.toggle_components(!show);
+ this.recent_order_list.toggle_component(show);
+ this.order_summary.toggle_component(show);
+ }
+
+ toggle_components(show) {
+ this.cart.toggle_component(show);
+ this.item_selector.toggle_component(show);
+
+ // do not show item details or payment if recent order is toggled off
+ !show ? (this.item_details.toggle_component(false) || this.payment.toggle_component(false)) : '';
+ }
+
+ make_new_invoice() {
+ return frappe.run_serially([
+ () => this.make_sales_invoice_frm(),
+ () => this.set_pos_profile_data(),
+ () => this.set_pos_profile_status(),
+ () => this.cart.load_invoice(),
+ ]);
+ }
+
+ make_sales_invoice_frm() {
+ const doctype = 'POS Invoice';
+ return new Promise(resolve => {
+ if (this.frm) {
+ this.frm = this.get_new_frm(this.frm);
+ this.frm.doc.items = [];
+ this.frm.doc.is_pos = 1
+ resolve();
+ } else {
+ frappe.model.with_doctype(doctype, () => {
+ this.frm = this.get_new_frm();
+ this.frm.doc.items = [];
+ this.frm.doc.is_pos = 1
+ resolve();
+ });
+ }
+ });
+ }
+
+ get_new_frm(_frm) {
+ const doctype = 'POS Invoice';
+ const page = $('<div>');
+ const frm = _frm || new frappe.ui.form.Form(doctype, page, false);
+ const name = frappe.model.make_new_doc_and_get_name(doctype, true);
+ frm.refresh(name);
+
+ return frm;
+ }
+
+ async make_return_invoice(doc) {
+ frappe.dom.freeze();
+ this.frm = this.get_new_frm(this.frm);
+ this.frm.doc.items = [];
+ const res = await frappe.call({
+ method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return",
+ args: {
+ 'source_name': doc.name,
+ 'target_doc': this.frm.doc
+ }
+ });
+ frappe.model.sync(res.message);
+ await this.set_pos_profile_data();
+ frappe.dom.unfreeze();
+ }
+
+ set_pos_profile_data() {
+ if (this.company && !this.frm.doc.company) this.frm.doc.company = this.company;
+ if (this.pos_profile && !this.frm.doc.pos_profile) this.frm.doc.pos_profile = this.pos_profile;
+ if (!this.frm.doc.company) return;
+
+ return new Promise(resolve => {
+ return this.frm.call({
+ doc: this.frm.doc,
+ method: "set_missing_values",
+ }).then((r) => {
+ if(!r.exc) {
+ if (!this.frm.doc.pos_profile) {
+ frappe.dom.unfreeze();
+ this.raise_exception_for_pos_profile();
+ }
+ this.frm.trigger("update_stock");
+ this.frm.trigger('calculate_taxes_and_totals');
+ if(this.frm.doc.taxes_and_charges) this.frm.script_manager.trigger("taxes_and_charges");
+ frappe.model.set_default_values(this.frm.doc);
+ if (r.message) {
+ this.frm.pos_print_format = r.message.print_format || "";
+ this.frm.meta.default_print_format = r.message.print_format || "";
+ this.frm.allow_edit_rate = r.message.allow_edit_rate;
+ this.frm.allow_edit_discount = r.message.allow_edit_discount;
+ this.frm.doc.campaign = r.message.campaign;
+ }
+ }
+ resolve();
+ });
+ });
+ }
+
+ raise_exception_for_pos_profile() {
+ setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000);
+ frappe.throw(__("POS Profile is required to use Point-of-Sale"));
+ }
+
+ set_invoice_status() {
+ const [status, indicator] = frappe.listview_settings["POS Invoice"].get_indicator(this.frm.doc);
+ this.page.set_indicator(__(`${status}`), indicator);
+ }
+
+ set_pos_profile_status() {
+ this.page.set_indicator(__(`${this.pos_profile}`), "blue");
+ }
+
+ async on_cart_update(args) {
+ frappe.dom.freeze();
+ try {
+ let { field, value, item } = args;
+ const { item_code, batch_no, serial_no, uom } = item;
+ let item_row = this.get_item_from_frm(item_code, batch_no, uom);
+
+ const item_selected_from_selector = field === 'qty' && value === "+1"
+
+ if (item_row) {
+ item_selected_from_selector && (value = item_row.qty + flt(value))
+
+ field === 'qty' && (value = flt(value));
+
+ if (field === 'qty' && value > 0 && !this.allow_negative_stock)
+ await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
+
+ if (this.is_current_item_being_edited(item_row) || item_selected_from_selector) {
+ await frappe.model.set_value(item_row.doctype, item_row.name, field, value);
+ this.update_cart_html(item_row);
+ }
+
+ } else {
+ if (!this.frm.doc.customer) {
+ frappe.dom.unfreeze();
+ frappe.show_alert({
+ message: __('You must select a customer before adding an item.'),
+ indicator: 'orange'
+ });
+ frappe.utils.play_sound("error");
+ return;
+ }
+ item_selected_from_selector && (value = flt(value))
+
+ const args = { item_code, batch_no, [field]: value };
+
+ if (serial_no) args['serial_no'] = serial_no;
+
+ if (field === 'serial_no') args['qty'] = value.split(`\n`).length || 0;
+
+ item_row = this.frm.add_child('items', args);
+
+ if (field === 'qty' && value !== 0 && !this.allow_negative_stock)
+ await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
+
+ await this.trigger_new_item_events(item_row);
+
+ this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
+ this.update_cart_html(item_row);
+ }
+ } catch (error) {
+ console.log(error);
+ } finally {
+ frappe.dom.unfreeze();
+ }
+ }
+
+ get_item_from_frm(item_code, batch_no, uom) {
+ const has_batch_no = batch_no;
+ return this.frm.doc.items.find(
+ i => i.item_code === item_code
+ && (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
+ && (i.uom === uom)
+ );
+ }
+
+ edit_item_details_of(item_row) {
+ this.item_details.toggle_item_details_section(item_row);
+ }
+
+ is_current_item_being_edited(item_row) {
+ const { item_code, batch_no } = this.item_details.current_item;
+
+ return item_code !== item_row.item_code || batch_no != item_row.batch_no ? false : true;
+ }
+
+ update_cart_html(item_row, remove_item) {
+ this.cart.update_item_html(item_row, remove_item);
+ this.cart.update_totals_section(this.frm);
+ }
+
+ check_serial_batch_selection_needed(item_row) {
+ // right now item details is shown for every type of item.
+ // if item details is not shown for every item then this fn will be needed
+ const serialized = item_row.has_serial_no;
+ const batched = item_row.has_batch_no;
+ const no_serial_selected = !item_row.serial_no;
+ const no_batch_selected = !item_row.batch_no;
+
+ if ((serialized && no_serial_selected) || (batched && no_batch_selected) ||
+ (serialized && batched && (no_batch_selected || no_serial_selected))) {
+ return true;
+ }
+ return false;
+ }
+
+ async trigger_new_item_events(item_row) {
+ await this.frm.script_manager.trigger('item_code', item_row.doctype, item_row.name)
+ await this.frm.script_manager.trigger('qty', item_row.doctype, item_row.name)
+ }
+
+ async check_stock_availability(item_row, qty_needed, warehouse) {
+ const available_qty = (await this.get_available_stock(item_row.item_code, warehouse)).message;
+
+ frappe.dom.unfreeze();
+ if (!(available_qty > 0)) {
+ frappe.model.clear_doc(item_row.doctype, item_row.name);
+ frappe.throw(__(`Item Code: ${item_row.item_code.bold()} is not available under warehouse ${warehouse.bold()}.`))
+ } else if (available_qty < qty_needed) {
+ frappe.show_alert({
+ message: __(`Stock quantity not enough for Item Code: ${item_row.item_code.bold()} under warehouse ${warehouse.bold()}.
+ Available quantity ${available_qty.toString().bold()}.`),
+ indicator: 'orange'
+ });
+ frappe.utils.play_sound("error");
+ this.item_details.qty_control.set_value(flt(available_qty));
+ }
+ frappe.dom.freeze();
+ }
+
+ get_available_stock(item_code, warehouse) {
+ const me = this;
+ return frappe.call({
+ method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.get_stock_availability",
+ args: {
+ 'item_code': item_code,
+ 'warehouse': warehouse,
+ },
+ callback(res) {
+ if (!me.item_stock_map[item_code])
+ me.item_stock_map[item_code] = {}
+ me.item_stock_map[item_code][warehouse] = res.message;
+ }
+ });
+ }
+
+ update_item_field(value, field_or_action) {
+ if (field_or_action === 'checkout') {
+ this.item_details.toggle_item_details_section(undefined);
+ } else if (field_or_action === 'remove') {
+ this.remove_item_from_cart();
+ } else {
+ const field_control = this.item_details[`${field_or_action}_control`];
+ if (!field_control) return;
+ field_control.set_focus();
+ value != "" && field_control.set_value(value);
+ }
+ }
+
+ remove_item_from_cart() {
+ frappe.dom.freeze();
+ const { doctype, name, current_item } = this.item_details;
+
+ frappe.model.set_value(doctype, name, 'qty', 0);
+
+ this.frm.script_manager.trigger('qty', doctype, name).then(() => {
+ frappe.model.clear_doc(doctype, name);
+ this.update_cart_html(current_item, true);
+ this.item_details.toggle_item_details_section(undefined);
+ frappe.dom.unfreeze();
+ })
+ }
+}
+
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
new file mode 100644
index 0000000..c23a6ad
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -0,0 +1,951 @@
+erpnext.PointOfSale.ItemCart = class {
+ constructor({ wrapper, events }) {
+ this.wrapper = wrapper;
+ this.events = events;
+ this.customer_info = undefined;
+
+ this.init_component();
+ }
+
+ init_component() {
+ this.prepare_dom();
+ this.init_child_components();
+ this.bind_events();
+ this.attach_shortcuts();
+ }
+
+ prepare_dom() {
+ this.wrapper.append(
+ `<section class="col-span-4 flex flex-col shadow rounded item-cart bg-white mx-h-70 h-100"></section>`
+ )
+
+ this.$component = this.wrapper.find('.item-cart');
+ }
+
+ init_child_components() {
+ this.init_customer_selector();
+ this.init_cart_components();
+ }
+
+ init_customer_selector() {
+ this.$component.append(
+ `<div class="customer-section rounded flex flex-col m-8 mb-0"></div>`
+ )
+ this.$customer_section = this.$component.find('.customer-section');
+ }
+
+ reset_customer_selector() {
+ const frm = this.events.get_frm();
+ frm.set_value('customer', '');
+ this.$customer_section.removeClass('border pr-4 pl-4');
+ this.make_customer_selector();
+ this.customer_field.set_focus();
+ }
+
+ init_cart_components() {
+ this.$component.append(
+ `<div class="cart-container flex flex-col items-center rounded flex-1 relative">
+ <div class="absolute flex flex-col p-8 pt-0 w-full h-full">
+ <div class="flex text-grey cart-header pt-2 pb-2 p-4 mt-2 mb-2 w-full f-shrink-0">
+ <div class="flex-1">Item</div>
+ <div class="mr-4">Qty</div>
+ <div class="rate-list-header mr-1 text-right">Amount</div>
+ </div>
+ <div class="cart-items-section flex flex-col flex-1 scroll-y rounded w-full"></div>
+ <div class="cart-totals-section flex flex-col w-full mt-4 f-shrink-0"></div>
+ <div class="numpad-section flex flex-col mt-4 d-none w-full p-8 pt-0 pb-0 f-shrink-0"></div>
+ </div>
+ </div>`
+ );
+ this.$cart_container = this.$component.find('.cart-container');
+
+ this.make_cart_totals_section();
+ this.make_cart_items_section();
+ this.make_cart_numpad();
+ }
+
+ make_cart_items_section() {
+ this.$cart_header = this.$component.find('.cart-header');
+ this.$cart_items_wrapper = this.$component.find('.cart-items-section');
+
+ this.make_no_items_placeholder();
+ }
+
+ make_no_items_placeholder() {
+ this.$cart_header.addClass('d-none');
+ this.$cart_items_wrapper.html(
+ `<div class="no-item-wrapper flex items-center h-18">
+ <div class="flex-1 text-center text-grey">No items in cart</div>
+ </div>`
+ )
+ this.$cart_items_wrapper.addClass('mt-4 border-grey border-dashed');
+ }
+
+ make_cart_totals_section() {
+ this.$totals_section = this.$component.find('.cart-totals-section');
+
+ this.$totals_section.append(
+ `<div class="add-discount flex items-center pt-4 pb-4 pr-4 pl-4 text-grey pointer no-select d-none">
+ + Add Discount
+ </div>
+ <div class="border border-grey rounded">
+ <div class="net-total flex justify-between items-center h-16 pr-8 pl-8 border-b-grey">
+ <div class="flex flex-col">
+ <div class="text-md text-dark-grey text-bold">Net Total</div>
+ </div>
+ <div class="flex flex-col text-right">
+ <div class="text-md text-dark-grey text-bold">0.00</div>
+ </div>
+ </div>
+ <div class="taxes"></div>
+ <div class="grand-total flex justify-between items-center h-16 pr-8 pl-8 border-b-grey">
+ <div class="flex flex-col">
+ <div class="text-md text-dark-grey text-bold">Grand Total</div>
+ </div>
+ <div class="flex flex-col text-right">
+ <div class="text-md text-dark-grey text-bold">0.00</div>
+ </div>
+ </div>
+ <div class="checkout-btn flex items-center justify-center h-16 pr-8 pl-8 text-center text-grey no-select pointer rounded-b text-md text-bold">
+ Checkout
+ </div>
+ <div class="edit-cart-btn flex items-center justify-center h-16 pr-8 pl-8 text-center text-grey no-select pointer d-none text-md text-bold">
+ Edit Cart
+ </div>
+ </div>`
+ )
+
+ this.$add_discount_elem = this.$component.find(".add-discount");
+ }
+
+ make_cart_numpad() {
+ this.$numpad_section = this.$component.find('.numpad-section');
+
+ this.number_pad = new erpnext.PointOfSale.NumberPad({
+ wrapper: this.$numpad_section,
+ events: {
+ numpad_event: this.on_numpad_event.bind(this)
+ },
+ cols: 5,
+ keys: [
+ [ 1, 2, 3, 'Quantity' ],
+ [ 4, 5, 6, 'Discount' ],
+ [ 7, 8, 9, 'Rate' ],
+ [ '.', 0, 'Delete', 'Remove' ]
+ ],
+ css_classes: [
+ [ '', '', '', 'col-span-2' ],
+ [ '', '', '', 'col-span-2' ],
+ [ '', '', '', 'col-span-2' ],
+ [ '', '', '', 'col-span-2 text-bold text-danger' ]
+ ],
+ fieldnames_map: { 'Quantity': 'qty', 'Discount': 'discount_percentage' }
+ })
+
+ this.$numpad_section.prepend(
+ `<div class="flex mb-2 justify-between">
+ <span class="numpad-net-total"></span>
+ <span class="numpad-grand-total"></span>
+ </div>`
+ )
+
+ this.$numpad_section.append(
+ `<div class="numpad-btn checkout-btn flex items-center justify-center h-16 pr-8 pl-8 bg-primary
+ text-center text-white no-select pointer rounded text-md text-bold mt-4" data-button-value="checkout">
+ Checkout
+ </div>`
+ )
+ }
+
+ bind_events() {
+ const me = this;
+ this.$customer_section.on('click', '.add-remove-customer', function (e) {
+ const customer_info_is_visible = me.$cart_container.hasClass('d-none');
+ customer_info_is_visible ?
+ me.toggle_customer_info(false) : me.reset_customer_selector();
+ });
+
+ this.$customer_section.on('click', '.customer-header', function(e) {
+ // don't triggger the event if .add-remove-customer btn is clicked which is under .customer-header
+ if ($(e.target).closest('.add-remove-customer').length) return;
+
+ const show = !me.$cart_container.hasClass('d-none');
+ me.toggle_customer_info(show);
+ });
+
+ this.$cart_items_wrapper.on('click', '.cart-item-wrapper', function() {
+ const $cart_item = $(this);
+
+ me.toggle_item_highlight(this);
+
+ const payment_section_hidden = me.$totals_section.find('.edit-cart-btn').hasClass('d-none');
+ if (!payment_section_hidden) {
+ // payment section is visible
+ // edit cart first and then open item details section
+ me.$totals_section.find(".edit-cart-btn").click();
+ }
+
+ const item_code = unescape($cart_item.attr('data-item-code'));
+ const batch_no = unescape($cart_item.attr('data-batch-no'));
+ const uom = unescape($cart_item.attr('data-uom'));
+ me.events.cart_item_clicked(item_code, batch_no, uom);
+ this.numpad_value = '';
+ });
+
+ this.$component.on('click', '.checkout-btn', function() {
+ if (!$(this).hasClass('bg-primary')) return;
+
+ me.events.checkout();
+ me.toggle_checkout_btn(false);
+
+ me.$add_discount_elem.removeClass("d-none");
+ });
+
+ this.$totals_section.on('click', '.edit-cart-btn', () => {
+ this.events.edit_cart();
+ this.toggle_checkout_btn(true);
+
+ this.$add_discount_elem.addClass("d-none");
+ });
+
+ this.$component.on('click', '.add-discount', () => {
+ const can_edit_discount = this.$add_discount_elem.find('.edit-discount').length;
+
+ if(!this.discount_field || can_edit_discount) this.show_discount_control();
+ });
+
+ frappe.ui.form.on("POS Invoice", "paid_amount", frm => {
+ // called when discount is applied
+ this.update_totals_section(frm);
+ });
+ }
+
+ attach_shortcuts() {
+ for (let row of this.number_pad.keys) {
+ for (let btn of row) {
+ let shortcut_key = `ctrl+${frappe.scrub(String(btn))[0]}`;
+ if (btn === 'Delete') shortcut_key = 'ctrl+backspace';
+ if (btn === 'Remove') shortcut_key = 'shift+ctrl+backspace'
+ if (btn === '.') shortcut_key = 'ctrl+>';
+
+ // to account for fieldname map
+ const fieldname = this.number_pad.fieldnames[btn] ? this.number_pad.fieldnames[btn] :
+ typeof btn === 'string' ? frappe.scrub(btn) : btn;
+
+ frappe.ui.keys.on(`${shortcut_key}`, () => {
+ const cart_is_visible = this.$component.is(":visible");
+ if (cart_is_visible && this.item_is_selected && this.$numpad_section.is(":visible")) {
+ this.$numpad_section.find(`.numpad-btn[data-button-value="${fieldname}"]`).click();
+ }
+ })
+ }
+ }
+
+ frappe.ui.keys.on("ctrl+enter", () => {
+ const cart_is_visible = this.$component.is(":visible");
+ const payment_section_hidden = this.$totals_section.find('.edit-cart-btn').hasClass('d-none');
+ if (cart_is_visible && payment_section_hidden) {
+ this.$component.find(".checkout-btn").click();
+ }
+ });
+ }
+
+ toggle_item_highlight(item) {
+ const $cart_item = $(item);
+ const item_is_highlighted = $cart_item.hasClass("shadow");
+
+ if (!item || item_is_highlighted) {
+ this.item_is_selected = false;
+ this.$cart_container.find('.cart-item-wrapper').removeClass("shadow").css("opacity", "1");
+ } else {
+ $cart_item.addClass("shadow");
+ this.item_is_selected = true;
+ this.$cart_container.find('.cart-item-wrapper').css("opacity", "1");
+ this.$cart_container.find('.cart-item-wrapper').not(item).removeClass("shadow").css("opacity", "0.65");
+ }
+ // highlight with inner shadow
+ // $cart_item.addClass("shadow-inner bg-selected");
+ // me.$cart_container.find('.cart-item-wrapper').not(this).removeClass("shadow-inner bg-selected");
+ }
+
+ make_customer_selector() {
+ this.$customer_section.html(`<div class="customer-search-field flex flex-1 items-center"></div>`);
+ const me = this;
+ const query = { query: 'erpnext.controllers.queries.customer_query' };
+ const allowed_customer_group = this.events.get_allowed_customer_group() || [];
+ if (allowed_customer_group.length) {
+ query.filters = {
+ customer_group: ['in', allowed_customer_group]
+ }
+ }
+ this.customer_field = frappe.ui.form.make_control({
+ df: {
+ label: __('Customer'),
+ fieldtype: 'Link',
+ options: 'Customer',
+ placeholder: __('Search by customer name, phone, email.'),
+ get_query: () => query,
+ onchange: function() {
+ if (this.value) {
+ const frm = me.events.get_frm();
+ frappe.dom.freeze();
+ frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'customer', this.value);
+ frm.script_manager.trigger('customer', frm.doc.doctype, frm.doc.name).then(() => {
+ frappe.run_serially([
+ () => me.fetch_customer_details(this.value),
+ () => me.events.customer_details_updated(me.customer_info),
+ () => me.update_customer_section(),
+ () => me.update_totals_section(),
+ () => frappe.dom.unfreeze()
+ ]);
+ })
+ }
+ },
+ },
+ parent: this.$customer_section.find('.customer-search-field'),
+ render_input: true,
+ });
+ this.customer_field.toggle_label(false);
+ }
+
+ fetch_customer_details(customer) {
+ if (customer) {
+ return new Promise((resolve) => {
+ frappe.db.get_value('Customer', customer, ["email_id", "mobile_no", "image", "loyalty_program"]).then(({ message }) => {
+ const { loyalty_program } = message;
+ // if loyalty program then fetch loyalty points too
+ if (loyalty_program) {
+ frappe.call({
+ method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details_with_points",
+ args: { customer, loyalty_program, "silent": true },
+ callback: (r) => {
+ const { loyalty_points, conversion_factor } = r.message;
+ if (!r.exc) {
+ this.customer_info = { ...message, customer, loyalty_points, conversion_factor };
+ resolve();
+ }
+ }
+ });
+ } else {
+ this.customer_info = { ...message, customer };
+ resolve();
+ }
+ });
+ });
+ } else {
+ return new Promise((resolve) => {
+ this.customer_info = {}
+ resolve();
+ });
+ }
+ }
+
+ show_discount_control() {
+ this.$add_discount_elem.removeClass("pr-4 pl-4");
+ this.$add_discount_elem.html(
+ `<div class="add-dicount-field flex flex-1 items-center"></div>
+ <div class="submit-field flex items-center"></div>`
+ );
+ const me = this;
+
+ this.discount_field = frappe.ui.form.make_control({
+ df: {
+ label: __('Discount'),
+ fieldtype: 'Data',
+ placeholder: __('Enter discount percentage.'),
+ onchange: function() {
+ if (this.value || this.value == 0) {
+ const frm = me.events.get_frm();
+ frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', this.value);
+ me.hide_discount_control(this.value);
+ }
+ },
+ },
+ parent: this.$add_discount_elem.find('.add-dicount-field'),
+ render_input: true,
+ });
+ this.discount_field.toggle_label(false);
+ this.discount_field.set_focus();
+ }
+
+ hide_discount_control(discount) {
+ this.$add_discount_elem.addClass('pr-4 pl-4');
+ this.$add_discount_elem.html(
+ `<svg class="mr-2" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"
+ stroke-linecap="round" stroke-linejoin="round">
+ <path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
+ </svg>
+ <div class="edit-discount p-1 pr-3 pl-3 text-dark-grey rounded w-fit bg-green-200 mb-2">
+ ${String(discount).bold()}% off
+ </div>
+ `
+ );
+ }
+
+ update_customer_section() {
+ const { customer, email_id='', mobile_no='', image } = this.customer_info || {};
+
+ if (customer) {
+ this.$customer_section.addClass('border pr-4 pl-4').html(
+ `<div class="customer-details flex flex-col">
+ <div class="customer-header flex items-center rounded h-18 pointer">
+ ${get_customer_image()}
+ <div class="customer-name flex flex-col flex-1 f-shrink-1 overflow-hidden whitespace-nowrap">
+ <div class="text-md text-dark-grey text-bold">${customer}</div>
+ ${get_customer_description()}
+ </div>
+ <div class="f-shrink-0 add-remove-customer flex items-center pointer" data-customer="${escape(customer)}">
+ <svg width="32" height="32" viewBox="0 0 14 14" fill="none">
+ <path d="M4.93764 4.93759L7.00003 6.99998M9.06243 9.06238L7.00003 6.99998M7.00003 6.99998L4.93764 9.06238L9.06243 4.93759" stroke="#8D99A6"/>
+ </svg>
+ </div>
+ </div>
+ </div>`
+ );
+ } else {
+ // reset customer selector
+ this.reset_customer_selector();
+ }
+
+ function get_customer_description() {
+ if (!email_id && !mobile_no) {
+ return `<div class="text-grey-200 italic">Click to add email / phone</div>`
+ } else if (email_id && !mobile_no) {
+ return `<div class="text-grey">${email_id}</div>`
+ } else if (mobile_no && !email_id) {
+ return `<div class="text-grey">${mobile_no}</div>`
+ } else {
+ return `<div class="text-grey">${email_id} | ${mobile_no}</div>`
+ }
+ }
+
+ function get_customer_image() {
+ if (image) {
+ return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200">
+ <img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">
+ </div>`
+ } else {
+ return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200 text-md">
+ ${frappe.get_abbr(customer)}
+ </div>`
+ }
+ }
+ }
+
+ update_totals_section(frm) {
+ if (!frm) frm = this.events.get_frm();
+
+ this.render_net_total(frm.doc.base_net_total);
+ this.render_grand_total(frm.doc.base_grand_total);
+
+ const taxes = frm.doc.taxes.map(t => { return { description: t.description, rate: t.rate }})
+ this.render_taxes(frm.doc.base_total_taxes_and_charges, taxes);
+ }
+
+ render_net_total(value) {
+ const currency = this.events.get_frm().doc.currency;
+ this.$totals_section.find('.net-total').html(
+ `<div class="flex flex-col">
+ <div class="text-md text-dark-grey text-bold">Net Total</div>
+ </div>
+ <div class="flex flex-col text-right">
+ <div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
+ </div>`
+ )
+
+ this.$numpad_section.find('.numpad-net-total').html(`Net Total: <span class="text-bold">${format_currency(value, currency)}</span>`)
+ }
+
+ render_grand_total(value) {
+ const currency = this.events.get_frm().doc.currency;
+ this.$totals_section.find('.grand-total').html(
+ `<div class="flex flex-col">
+ <div class="text-md text-dark-grey text-bold">Grand Total</div>
+ </div>
+ <div class="flex flex-col text-right">
+ <div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
+ </div>`
+ )
+
+ this.$numpad_section.find('.numpad-grand-total').html(`Grand Total: <span class="text-bold">${format_currency(value, currency)}</span>`)
+ }
+
+ render_taxes(value, taxes) {
+ if (taxes.length) {
+ const currency = this.events.get_frm().doc.currency;
+ this.$totals_section.find('.taxes').html(
+ `<div class="flex items-center justify-between h-16 pr-8 pl-8 border-b-grey">
+ <div class="flex">
+ <div class="text-md text-dark-grey text-bold w-fit">Tax Charges</div>
+ <div class="flex ml-6 text-dark-grey">
+ ${
+ taxes.map((t, i) => {
+ let margin_left = '';
+ if (i !== 0) margin_left = 'ml-2';
+ return `<span class="border-grey p-1 pl-2 pr-2 rounded ${margin_left}">${t.description}</span>`
+ }).join('')
+ }
+ </div>
+ </div>
+ <div class="flex flex-col text-right">
+ <div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
+ </div>
+ </div>`
+ )
+ } else {
+ this.$totals_section.find('.taxes').html('')
+ }
+ }
+
+ get_cart_item({ item_code, batch_no, uom }) {
+ const batch_attr = `[data-batch-no="${escape(batch_no)}"]`;
+ const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
+ const uom_attr = `[data-uom=${escape(uom)}]`;
+
+ const item_selector = batch_no ?
+ `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`;
+
+ return this.$cart_items_wrapper.find(item_selector);
+ }
+
+ update_item_html(item, remove_item) {
+ const $item = this.get_cart_item(item);
+
+ if (remove_item) {
+ $item && $item.remove();
+ } else {
+ const { item_code, batch_no, uom } = item;
+ const search_field = batch_no ? 'batch_no' : 'item_code';
+ const search_value = batch_no || item_code;
+ const item_row = this.events.get_frm().doc.items.find(i => i[search_field] === search_value && i.uom === uom);
+
+ this.render_cart_item(item_row, $item);
+ }
+
+ const no_of_cart_items = this.$cart_items_wrapper.children().length;
+ no_of_cart_items > 0 && this.highlight_checkout_btn(no_of_cart_items > 0);
+
+ this.update_empty_cart_section(no_of_cart_items);
+ }
+
+ render_cart_item(item_data, $item_to_update) {
+ const currency = this.events.get_frm().doc.currency;
+ const me = this;
+
+ if (!$item_to_update.length) {
+ this.$cart_items_wrapper.append(
+ `<div class="cart-item-wrapper flex items-center h-18 pr-4 pl-4 rounded border-grey pointer no-select"
+ data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
+ data-batch-no="${escape(item_data.batch_no || '')}">
+ </div>`
+ )
+ $item_to_update = this.get_cart_item(item_data);
+ }
+
+ $item_to_update.html(
+ `<div class="flex flex-col flex-1 f-shrink-1 overflow-hidden whitespace-nowrap">
+ <div class="text-md text-dark-grey text-bold">
+ ${item_data.item_name}
+ </div>
+ ${get_description_html()}
+ </div>
+ ${get_rate_discount_html()}
+ </div>`
+ )
+
+ set_dynamic_rate_header_width();
+ this.scroll_to_item($item_to_update);
+
+ function set_dynamic_rate_header_width() {
+ const rate_cols = Array.from(me.$cart_items_wrapper.find(".rate-col"));
+ me.$cart_header.find(".rate-list-header").css("width", "");
+ me.$cart_items_wrapper.find(".rate-col").css("width", "");
+ let max_width = rate_cols.reduce((max_width, elm) => {
+ if ($(elm).width() > max_width)
+ max_width = $(elm).width();
+ return max_width;
+ }, 0);
+
+ max_width += 1;
+ if (max_width == 1) max_width = "";
+
+ me.$cart_header.find(".rate-list-header").css("width", max_width);
+ me.$cart_items_wrapper.find(".rate-col").css("width", max_width);
+ }
+
+ function get_rate_discount_html() {
+ if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) {
+ return `
+ <div class="flex f-shrink-0 ml-4 items-center">
+ <div class="flex w-8 h-8 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
+ <span>${item_data.qty || 0}</span>
+ </div>
+ <div class="rate-col flex flex-col f-shrink-0 text-right">
+ <div class="text-md text-dark-grey text-bold">${format_currency(item_data.amount, currency)}</div>
+ <div class="text-md-0 text-dark-grey">${format_currency(item_data.rate, currency)}</div>
+ </div>
+ </div>`
+ } else {
+ return `
+ <div class="flex f-shrink-0 ml-4 text-right">
+ <div class="flex w-8 h-8 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
+ <span>${item_data.qty || 0}</span>
+ </div>
+ <div class="rate-col flex flex-col f-shrink-0 text-right">
+ <div class="text-md text-dark-grey text-bold">${format_currency(item_data.rate, currency)}</div>
+ </div>
+ </div>`
+ }
+ }
+
+ function get_description_html() {
+ if (item_data.description) {
+ if (item_data.description.indexOf('<div>') != -1) {
+ try {
+ item_data.description = $(item_data.description).text();
+ } catch (error) {
+ item_data.description = item_data.description.replace(/<div>/g, ' ').replace(/<\/div>/g, ' ').replace(/ +/g, ' ');
+ }
+ }
+ item_data.description = frappe.ellipsis(item_data.description, 45);
+ return `<div class="text-grey">${item_data.description}</div>`
+ }
+ return ``;
+ }
+ }
+
+ scroll_to_item($item) {
+ if ($item.length === 0) return;
+ const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop();
+ this.$cart_items_wrapper.animate({ scrollTop });
+ }
+
+ update_selector_value_in_cart_item(selector, value, item) {
+ const $item_to_update = this.get_cart_item(item);
+ $item_to_update.attr(`data-${selector}`, value);
+ }
+
+ toggle_checkout_btn(show_checkout) {
+ if (show_checkout) {
+ this.$totals_section.find('.checkout-btn').removeClass('d-none');
+ this.$totals_section.find('.edit-cart-btn').addClass('d-none');
+ } else {
+ this.$totals_section.find('.checkout-btn').addClass('d-none');
+ this.$totals_section.find('.edit-cart-btn').removeClass('d-none');
+ }
+ }
+
+ highlight_checkout_btn(toggle) {
+ const has_primary_class = this.$totals_section.find('.checkout-btn').hasClass('bg-primary');
+ if (toggle && !has_primary_class) {
+ this.$totals_section.find('.checkout-btn').addClass('bg-primary text-white text-lg');
+ } else if (!toggle && has_primary_class) {
+ this.$totals_section.find('.checkout-btn').removeClass('bg-primary text-white text-lg');
+ }
+ }
+
+ update_empty_cart_section(no_of_cart_items) {
+ const $no_item_element = this.$cart_items_wrapper.find('.no-item-wrapper');
+
+ // if cart has items and no item is present
+ no_of_cart_items > 0 && $no_item_element && $no_item_element.remove()
+ && this.$cart_items_wrapper.removeClass('mt-4 border-grey border-dashed') && this.$cart_header.removeClass('d-none');
+
+ no_of_cart_items === 0 && !$no_item_element.length && this.make_no_items_placeholder();
+ }
+
+ on_numpad_event($btn) {
+ const current_action = $btn.attr('data-button-value');
+ const action_is_field_edit = ['qty', 'discount_percentage', 'rate'].includes(current_action);
+
+ this.highlight_numpad_btn($btn, current_action);
+
+ const action_is_pressed_twice = this.prev_action === current_action;
+ const first_click_event = !this.prev_action;
+ const field_to_edit_changed = this.prev_action && this.prev_action != current_action;
+
+ if (action_is_field_edit) {
+
+ if (first_click_event || field_to_edit_changed) {
+ this.prev_action = current_action;
+ } else if (action_is_pressed_twice) {
+ this.prev_action = undefined;
+ }
+ this.numpad_value = '';
+
+ } else if (current_action === 'checkout') {
+ this.prev_action = undefined;
+ this.toggle_item_highlight();
+ this.events.numpad_event(undefined, current_action);
+ return;
+ } else if (current_action === 'remove') {
+ this.prev_action = undefined;
+ this.toggle_item_highlight();
+ this.events.numpad_event(undefined, current_action);
+ return;
+ } else {
+ this.numpad_value = current_action === 'delete' ? this.numpad_value.slice(0, -1) : this.numpad_value + current_action;
+ this.numpad_value = this.numpad_value || 0;
+ }
+
+ const first_click_event_is_not_field_edit = !action_is_field_edit && first_click_event;
+
+ if (first_click_event_is_not_field_edit) {
+ frappe.show_alert({
+ indicator: 'red',
+ message: __('Please select a field to edit from numpad')
+ });
+ frappe.utils.play_sound("error");
+ return;
+ }
+
+ if (flt(this.numpad_value) > 100 && this.prev_action === 'discount_percentage') {
+ frappe.show_alert({
+ message: __('Discount cannot be greater than 100%'),
+ indicator: 'orange'
+ });
+ frappe.utils.play_sound("error");
+ this.numpad_value = current_action;
+ }
+
+ this.events.numpad_event(this.numpad_value, this.prev_action);
+ }
+
+ highlight_numpad_btn($btn, curr_action) {
+ const curr_action_is_highlighted = $btn.hasClass('shadow-inner');
+ const curr_action_is_action = ['qty', 'discount_percentage', 'rate', 'done'].includes(curr_action);
+
+ if (!curr_action_is_highlighted) {
+ $btn.addClass('shadow-inner bg-selected');
+ }
+ if (this.prev_action === curr_action && curr_action_is_highlighted) {
+ // if Qty is pressed twice
+ $btn.removeClass('shadow-inner bg-selected');
+ }
+ if (this.prev_action && this.prev_action !== curr_action && curr_action_is_action) {
+ // Order: Qty -> Rate then remove Qty highlight
+ const prev_btn = $(`[data-button-value='${this.prev_action}']`);
+ prev_btn.removeClass('shadow-inner bg-selected');
+ }
+ if (!curr_action_is_action || curr_action === 'done') {
+ // if numbers are clicked
+ setTimeout(() => {
+ $btn.removeClass('shadow-inner bg-selected');
+ }, 100);
+ }
+ }
+
+ toggle_numpad(show) {
+ if (show) {
+ this.$totals_section.addClass('d-none');
+ this.$numpad_section.removeClass('d-none');
+ } else {
+ this.$totals_section.removeClass('d-none');
+ this.$numpad_section.addClass('d-none');
+ }
+ this.reset_numpad();
+ }
+
+ reset_numpad() {
+ this.numpad_value = '';
+ this.prev_action = undefined;
+ this.$numpad_section.find('.shadow-inner').removeClass('shadow-inner bg-selected');
+ }
+
+ toggle_numpad_field_edit(fieldname) {
+ if (['qty', 'discount_percentage', 'rate'].includes(fieldname)) {
+ this.$numpad_section.find(`[data-button-value="${fieldname}"]`).click();
+ }
+ }
+
+ toggle_customer_info(show) {
+ if (show) {
+ this.$cart_container.addClass('d-none')
+ this.$customer_section.addClass('flex-1 scroll-y').removeClass('mb-0 border pr-4 pl-4')
+ this.$customer_section.find('.icon').addClass('w-24 h-24 text-2xl').removeClass('w-12 h-12 text-md')
+ this.$customer_section.find('.customer-header').removeClass('h-18');
+ this.$customer_section.find('.customer-details').addClass('sticky z-100 bg-white');
+
+ this.$customer_section.find('.customer-name').html(
+ `<div class="text-md text-dark-grey text-bold">${this.customer_info.customer}</div>
+ <div class="last-transacted-on text-grey-200"></div>`
+ )
+
+ this.$customer_section.find('.customer-details').append(
+ `<div class="customer-form">
+ <div class="text-grey mt-4 mb-6">CONTACT DETAILS</div>
+ <div class="grid grid-cols-2 gap-4">
+ <div class="email_id-field"></div>
+ <div class="mobile_no-field"></div>
+ <div class="loyalty_program-field"></div>
+ <div class="loyalty_points-field"></div>
+ </div>
+ <div class="text-grey mt-4 mb-6">RECENT TRANSACTIONS</div>
+ </div>`
+ )
+ // transactions need to be in diff div from sticky elem for scrolling
+ this.$customer_section.append(`<div class="customer-transactions flex-1 rounded"></div>`)
+
+ this.render_customer_info_form();
+ this.fetch_customer_transactions();
+
+ } else {
+ this.$cart_container.removeClass('d-none');
+ this.$customer_section.removeClass('flex-1 scroll-y').addClass('mb-0 border pr-4 pl-4');
+ this.$customer_section.find('.icon').addClass('w-12 h-12 text-md').removeClass('w-24 h-24 text-2xl');
+ this.$customer_section.find('.customer-header').addClass('h-18')
+ this.$customer_section.find('.customer-details').removeClass('sticky z-100 bg-white');
+
+ this.update_customer_section();
+ }
+ }
+
+ render_customer_info_form() {
+ const $customer_form = this.$customer_section.find('.customer-form');
+
+ const dfs = [{
+ fieldname: 'email_id',
+ label: __('Email'),
+ fieldtype: 'Data',
+ options: 'email',
+ placeholder: __("Enter customer's email")
+ },{
+ fieldname: 'mobile_no',
+ label: __('Phone Number'),
+ fieldtype: 'Data',
+ placeholder: __("Enter customer's phone number")
+ },{
+ fieldname: 'loyalty_program',
+ label: __('Loyalty Program'),
+ fieldtype: 'Link',
+ options: 'Loyalty Program',
+ placeholder: __("Select Loyalty Program")
+ },{
+ fieldname: 'loyalty_points',
+ label: __('Loyalty Points'),
+ fieldtype: 'Int',
+ read_only: 1
+ }];
+
+ const me = this;
+ dfs.forEach(df => {
+ this[`customer_${df.fieldname}_field`] = frappe.ui.form.make_control({
+ df: { ...df,
+ onchange: handle_customer_field_change,
+ },
+ parent: $customer_form.find(`.${df.fieldname}-field`),
+ render_input: true,
+ });
+ this[`customer_${df.fieldname}_field`].set_value(this.customer_info[df.fieldname]);
+ })
+
+ function handle_customer_field_change() {
+ const current_value = me.customer_info[this.df.fieldname];
+ const current_customer = me.customer_info.customer;
+
+ if (this.value && current_value != this.value && this.df.fieldname != 'loyalty_points') {
+ frappe.call({
+ method: 'erpnext.selling.page.point_of_sale.point_of_sale.set_customer_info',
+ args: {
+ fieldname: this.df.fieldname,
+ customer: current_customer,
+ value: this.value
+ },
+ callback: (r) => {
+ if(!r.exc) {
+ me.customer_info[this.df.fieldname] = this.value;
+ frappe.show_alert({
+ message: __("Customer contact updated successfully."),
+ indicator: 'green'
+ });
+ frappe.utils.play_sound("submit");
+ }
+ }
+ });
+ }
+ }
+ }
+
+ fetch_customer_transactions() {
+ frappe.db.get_list('POS Invoice', {
+ filters: { customer: this.customer_info.customer, docstatus: 1 },
+ fields: ['name', 'grand_total', 'status', 'posting_date', 'posting_time', 'currency'],
+ limit: 20
+ }).then((res) => {
+ const transaction_container = this.$customer_section.find('.customer-transactions');
+
+ if (!res.length) {
+ transaction_container.removeClass('flex-1 border rounded').html(
+ `<div class="text-grey text-center">No recent transactions found</div>`
+ )
+ return;
+ };
+
+ const elapsed_time = moment(res[0].posting_date+" "+res[0].posting_time).fromNow();
+ this.$customer_section.find('.last-transacted-on').html(`Last transacted ${elapsed_time}`);
+
+ res.forEach(invoice => {
+ const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma");
+ let indicator_color = '';
+
+ if (in_list(['Paid', 'Consolidated'], invoice.status)) (indicator_color = 'green');
+ if (invoice.status === 'Draft') (indicator_color = 'red');
+ if (invoice.status === 'Return') (indicator_color = 'grey');
+
+ transaction_container.append(
+ `<div class="invoice-wrapper flex p-3 justify-between border-grey rounded pointer no-select" data-invoice-name="${escape(invoice.name)}">
+ <div class="flex flex-col justify-end">
+ <div class="text-dark-grey text-bold overflow-hidden whitespace-nowrap mb-2">${invoice.name}</div>
+ <div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
+ ${posting_datetime}
+ </div>
+ </div>
+ <div class="flex flex-col text-right">
+ <div class="f-shrink-0 text-md text-dark-grey text-bold ml-4">
+ ${format_currency(invoice.grand_total, invoice.currency, 0) || 0}
+ </div>
+ <div class="f-shrink-0 text-grey ml-4 text-bold indicator ${indicator_color}">${invoice.status}</div>
+ </div>
+ </div>`
+ )
+ });
+ })
+ }
+
+ load_invoice() {
+ const frm = this.events.get_frm();
+ this.fetch_customer_details(frm.doc.customer).then(() => {
+ this.events.customer_details_updated(this.customer_info);
+ this.update_customer_section();
+ })
+
+ this.$cart_items_wrapper.html('');
+ if (frm.doc.items.length) {
+ frm.doc.items.forEach(item => {
+ this.update_item_html(item);
+ });
+ } else {
+ this.make_no_items_placeholder();
+ this.highlight_checkout_btn(false);
+ }
+
+ this.update_totals_section(frm);
+
+ if(frm.doc.docstatus === 1) {
+ this.$totals_section.find('.checkout-btn').addClass('d-none');
+ this.$totals_section.find('.edit-cart-btn').addClass('d-none');
+ this.$totals_section.find('.grand-total').removeClass('border-b-grey');
+ } else {
+ this.$totals_section.find('.checkout-btn').removeClass('d-none');
+ this.$totals_section.find('.edit-cart-btn').addClass('d-none');
+ this.$totals_section.find('.grand-total').addClass('border-b-grey');
+ }
+
+ this.toggle_component(true);
+ }
+
+ toggle_component(show) {
+ show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
+ }
+
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
new file mode 100644
index 0000000..86a1be9
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -0,0 +1,394 @@
+erpnext.PointOfSale.ItemDetails = class {
+ constructor({ wrapper, events }) {
+ this.wrapper = wrapper;
+ this.events = events;
+ this.current_item = {};
+
+ this.init_component();
+ }
+
+ init_component() {
+ this.prepare_dom();
+ this.init_child_components();
+ this.bind_events();
+ this.attach_shortcuts();
+ }
+
+ prepare_dom() {
+ this.wrapper.append(
+ `<section class="col-span-4 flex shadow rounded item-details bg-white mx-h-70 h-100 d-none"></section>`
+ )
+
+ this.$component = this.wrapper.find('.item-details');
+ }
+
+ init_child_components() {
+ this.$component.html(
+ `<div class="details-container flex flex-col p-8 rounded w-full">
+ <div class="flex justify-between mb-2">
+ <div class="text-grey">ITEM DETAILS</div>
+ <div class="close-btn text-grey hover-underline pointer no-select">Close</div>
+ </div>
+ <div class="item-defaults flex">
+ <div class="flex-1 flex flex-col justify-end mr-4 mb-2">
+ <div class="item-name text-xl font-weight-450"></div>
+ <div class="item-description text-md-0 text-grey-200"></div>
+ <div class="item-price text-xl font-bold"></div>
+ </div>
+ <div class="item-image flex items-center justify-center w-46 h-46 bg-light-grey rounded ml-4 text-6xl text-grey-100"></div>
+ </div>
+ <div class="discount-section flex items-center"></div>
+ <div class="text-grey mt-4 mb-6">STOCK DETAILS</div>
+ <div class="form-container grid grid-cols-2 row-gap-2 col-gap-4 grid-auto-row"></div>
+ </div>`
+ )
+
+ this.$item_name = this.$component.find('.item-name');
+ this.$item_description = this.$component.find('.item-description');
+ this.$item_price = this.$component.find('.item-price');
+ this.$item_image = this.$component.find('.item-image');
+ this.$form_container = this.$component.find('.form-container');
+ this.$dicount_section = this.$component.find('.discount-section');
+ }
+
+ toggle_item_details_section(item) {
+ const { item_code, batch_no, uom } = this.current_item;
+ const item_code_is_same = item && item_code === item.item_code;
+ const batch_is_same = item && batch_no == item.batch_no;
+ const uom_is_same = item && uom === item.uom;
+
+ this.item_has_changed = !item ? false : item_code_is_same && batch_is_same && uom_is_same ? false : true;
+
+ this.events.toggle_item_selector(this.item_has_changed);
+ this.toggle_component(this.item_has_changed);
+
+ if (this.item_has_changed) {
+ this.doctype = item.doctype;
+ this.item_meta = frappe.get_meta(this.doctype);
+ this.name = item.name;
+ this.item_row = item;
+ this.currency = this.events.get_frm().doc.currency;
+
+ this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom };
+
+ this.render_dom(item);
+ this.render_discount_dom(item);
+ this.render_form(item);
+ } else {
+ this.validate_serial_batch_item();
+ this.current_item = {};
+ }
+ }
+
+ validate_serial_batch_item() {
+ const doc = this.events.get_frm().doc;
+ const item_row = doc.items.find(item => item.name === this.name);
+
+ if (!item_row) return;
+
+ const serialized = item_row.has_serial_no;
+ const batched = item_row.has_batch_no;
+ const no_serial_selected = !item_row.serial_no;
+ const no_batch_selected = !item_row.batch_no;
+
+ if ((serialized && no_serial_selected) || (batched && no_batch_selected) ||
+ (serialized && batched && (no_batch_selected || no_serial_selected))) {
+
+ frappe.show_alert({
+ message: __("Item will be removed since no serial / batch no selected."),
+ indicator: 'orange'
+ });
+ frappe.utils.play_sound("cancel");
+ this.events.remove_item_from_cart();
+ }
+ }
+
+ render_dom(item) {
+ let { item_code ,item_name, description, image, price_list_rate } = item;
+
+ function get_description_html() {
+ if (description) {
+ description = description.indexOf('...') === -1 && description.length > 75 ? description.substr(0, 73) + '...' : description;
+ return description;
+ }
+ return ``;
+ }
+
+ this.$item_name.html(item_name);
+ this.$item_description.html(get_description_html());
+ this.$item_price.html(format_currency(price_list_rate, this.currency));
+ if (image) {
+ this.$item_image.html(
+ `<img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">`
+ );
+ } else {
+ this.$item_image.html(frappe.get_abbr(item_code));
+ }
+
+ }
+
+ render_discount_dom(item) {
+ if (item.discount_percentage) {
+ this.$dicount_section.html(
+ `<div class="text-grey line-through mr-4 text-md mb-2">
+ ${format_currency(item.price_list_rate, this.currency)}
+ </div>
+ <div class="p-1 pr-3 pl-3 rounded w-fit text-bold bg-green-200 mb-2">
+ ${item.discount_percentage}% off
+ </div>`
+ )
+ this.$item_price.html(format_currency(item.rate, this.currency));
+ } else {
+ this.$dicount_section.html(``)
+ }
+ }
+
+ render_form(item) {
+ const fields_to_display = this.get_form_fields(item);
+ this.$form_container.html('');
+
+ fields_to_display.forEach((fieldname, idx) => {
+ this.$form_container.append(
+ `<div class="">
+ <div class="item_detail_field ${fieldname}-control" data-fieldname="${fieldname}"></div>
+ </div>`
+ )
+
+ const field_meta = this.item_meta.fields.find(df => df.fieldname === fieldname);
+ fieldname === 'discount_percentage' ? (field_meta.label = __('Discount (%)')) : '';
+ const me = this;
+
+ this[`${fieldname}_control`] = frappe.ui.form.make_control({
+ df: {
+ ...field_meta,
+ onchange: function() {
+ me.events.form_updated(me.doctype, me.name, fieldname, this.value);
+ }
+ },
+ parent: this.$form_container.find(`.${fieldname}-control`),
+ render_input: true,
+ })
+ this[`${fieldname}_control`].set_value(item[fieldname]);
+ });
+
+ this.make_auto_serial_selection_btn(item);
+
+ this.bind_custom_control_change_event();
+ }
+
+ get_form_fields(item) {
+ const fields = ['qty', 'uom', 'rate', 'price_list_rate', 'discount_percentage', 'warehouse', 'actual_qty'];
+ if (item.has_serial_no) fields.push('serial_no');
+ if (item.has_batch_no) fields.push('batch_no');
+ return fields;
+ }
+
+ make_auto_serial_selection_btn(item) {
+ if (item.has_serial_no) {
+ this.$form_container.append(
+ `<div class="grid-filler no-select"></div>`
+ )
+ if (!item.has_batch_no) {
+ this.$form_container.append(
+ `<div class="grid-filler no-select"></div>`
+ )
+ }
+ this.$form_container.append(
+ `<div class="auto-fetch-btn bg-grey-100 border border-grey text-bold rounded pt-3 pb-3 pl-6 pr-8 text-grey pointer no-select mt-2"
+ style="height: 3.3rem">
+ Auto Fetch Serial Numbers
+ </div>`
+ )
+ this.$form_container.find('.serial_no-control').find('textarea').css('height', '9rem');
+ this.$form_container.find('.serial_no-control').parent().addClass('row-span-2');
+ }
+ }
+
+ bind_custom_control_change_event() {
+ const me = this;
+ if (this.rate_control) {
+ this.rate_control.df.onchange = function() {
+ if (this.value) {
+ me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
+ const item_row = frappe.get_doc(me.doctype, me.name);
+ const doc = me.events.get_frm().doc;
+
+ me.$item_price.html(format_currency(item_row.rate, doc.currency));
+ me.render_discount_dom(item_row);
+ });
+ }
+ }
+ }
+
+ if (this.warehouse_control) {
+ this.warehouse_control.df.reqd = 1;
+ this.warehouse_control.df.onchange = function() {
+ if (this.value) {
+ me.events.form_updated(me.doctype, me.name, 'warehouse', this.value).then(() => {
+ me.item_stock_map = me.events.get_item_stock_map();
+ const available_qty = me.item_stock_map[me.item_row.item_code][this.value];
+ if (available_qty === undefined) {
+ me.events.get_available_stock(me.item_row.item_code, this.value).then(() => {
+ // item stock map is updated now reset warehouse
+ me.warehouse_control.set_value(this.value);
+ })
+ } else if (available_qty === 0) {
+ me.warehouse_control.set_value('');
+ frappe.throw(__(`Item Code: ${me.item_row.item_code.bold()} is not available under warehouse ${this.value.bold()}.`));
+ }
+ me.actual_qty_control.set_value(available_qty);
+ });
+ }
+ }
+ this.warehouse_control.refresh();
+ }
+
+ if (this.discount_percentage_control) {
+ this.discount_percentage_control.df.onchange = function() {
+ if (this.value) {
+ me.events.form_updated(me.doctype, me.name, 'discount_percentage', this.value).then(() => {
+ const item_row = frappe.get_doc(me.doctype, me.name);
+ me.rate_control.set_value(item_row.rate);
+ });
+ }
+ }
+ }
+
+ if (this.serial_no_control) {
+ this.serial_no_control.df.reqd = 1;
+ this.serial_no_control.df.onchange = async function() {
+ !me.current_item.batch_no && await me.auto_update_batch_no();
+ me.events.form_updated(me.doctype, me.name, 'serial_no', this.value);
+ }
+ this.serial_no_control.refresh();
+ }
+
+ if (this.batch_no_control) {
+ this.batch_no_control.df.reqd = 1;
+ this.batch_no_control.df.get_query = () => {
+ return {
+ query: 'erpnext.controllers.queries.get_batch_no',
+ filters: {
+ item_code: me.item_row.item_code,
+ warehouse: me.item_row.warehouse
+ }
+ }
+ };
+ this.batch_no_control.df.onchange = function() {
+ me.events.set_value_in_current_cart_item('batch-no', this.value);
+ me.events.form_updated(me.doctype, me.name, 'batch_no', this.value);
+ me.current_item.batch_no = this.value;
+ }
+ this.batch_no_control.refresh();
+ }
+
+ if (this.uom_control) {
+ this.uom_control.df.onchange = function() {
+ me.events.set_value_in_current_cart_item('uom', this.value);
+ me.events.form_updated(me.doctype, me.name, 'uom', this.value);
+ me.current_item.uom = this.value;
+ }
+ }
+ }
+
+ async auto_update_batch_no() {
+ if (this.serial_no_control && this.batch_no_control) {
+ const selected_serial_nos = this.serial_no_control.get_value().split(`\n`).filter(s => s);
+ if (!selected_serial_nos.length) return;
+
+ // find batch nos of the selected serial no
+ const serials_with_batch_no = await frappe.db.get_list("Serial No", {
+ filters: { 'name': ["in", selected_serial_nos]},
+ fields: ["batch_no", "name"]
+ });
+ const batch_serial_map = serials_with_batch_no.reduce((acc, r) => {
+ acc[r.batch_no] || (acc[r.batch_no] = []);
+ acc[r.batch_no] = [...acc[r.batch_no], r.name];
+ return acc;
+ }, {});
+ // set current item's batch no and serial no
+ const batch_no = Object.keys(batch_serial_map)[0];
+ const batch_serial_nos = batch_serial_map[batch_no].join(`\n`);
+ // eg. 10 selected serial no. -> 5 belongs to first batch other 5 belongs to second batch
+ const serial_nos_belongs_to_other_batch = selected_serial_nos.length !== batch_serial_map[batch_no].length;
+
+ const current_batch_no = this.batch_no_control.get_value();
+ current_batch_no != batch_no && await this.batch_no_control.set_value(batch_no);
+
+ if (serial_nos_belongs_to_other_batch) {
+ this.serial_no_control.set_value(batch_serial_nos);
+ this.qty_control.set_value(batch_serial_map[batch_no].length);
+ }
+
+ delete batch_serial_map[batch_no];
+
+ if (serial_nos_belongs_to_other_batch)
+ this.events.clone_new_batch_item_in_frm(batch_serial_map, this.current_item);
+ }
+ }
+
+ bind_events() {
+ this.bind_auto_serial_fetch_event();
+ this.bind_fields_to_numpad_fields();
+
+ this.$component.on('click', '.close-btn', () => {
+ this.events.close_item_details();
+ });
+ }
+
+ attach_shortcuts() {
+ frappe.ui.keys.on("escape", () => {
+ const item_details_visible = this.$component.is(":visible");
+ if (item_details_visible) {
+ this.events.close_item_details();
+ }
+ });
+ }
+
+ bind_fields_to_numpad_fields() {
+ const me = this;
+ this.$form_container.on('click', '.input-with-feedback', function() {
+ const fieldname = $(this).attr('data-fieldname');
+ if (this.last_field_focused != fieldname) {
+ me.events.item_field_focused(fieldname);
+ this.last_field_focused = fieldname;
+ }
+ });
+ }
+
+ bind_auto_serial_fetch_event() {
+ this.$form_container.on('click', '.auto-fetch-btn', () => {
+ this.batch_no_control.set_value('');
+ let qty = this.qty_control.get_value();
+ let numbers = frappe.call({
+ method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number",
+ args: {
+ qty,
+ item_code: this.current_item.item_code,
+ warehouse: this.warehouse_control.get_value() || '',
+ batch_nos: this.current_item.batch_no || '',
+ for_doctype: 'POS Invoice'
+ }
+ });
+
+ numbers.then((data) => {
+ let auto_fetched_serial_numbers = data.message;
+ let records_length = auto_fetched_serial_numbers.length;
+ if (!records_length) {
+ const warehouse = this.warehouse_control.get_value().bold();
+ frappe.msgprint(__(`Serial numbers unavailable for Item ${this.current_item.item_code.bold()}
+ under warehouse ${warehouse}. Please try changing warehouse.`));
+ } else if (records_length < qty) {
+ frappe.msgprint(`Fetched only ${records_length} available serial numbers.`);
+ this.qty_control.set_value(records_length);
+ }
+ numbers = auto_fetched_serial_numbers.join(`\n`);
+ this.serial_no_control.set_value(numbers);
+ });
+ })
+ }
+
+ toggle_component(show) {
+ show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
+ }
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
new file mode 100644
index 0000000..ee0c06d
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -0,0 +1,265 @@
+erpnext.PointOfSale.ItemSelector = class {
+ constructor({ frm, wrapper, events, pos_profile }) {
+ this.wrapper = wrapper;
+ this.events = events;
+ this.pos_profile = pos_profile;
+
+ this.inti_component();
+ }
+
+ inti_component() {
+ this.prepare_dom();
+ this.make_search_bar();
+ this.load_items_data();
+ this.bind_events();
+ this.attach_shortcuts();
+ }
+
+ prepare_dom() {
+ this.wrapper.append(
+ `<section class="col-span-6 flex shadow rounded items-selector bg-white mx-h-70 h-100">
+ <div class="flex flex-col rounded w-full scroll-y">
+ <div class="filter-section flex p-8 pb-2 bg-white sticky z-100">
+ <div class="search-field flex f-grow-3 mr-8 items-center text-grey"></div>
+ <div class="item-group-field flex f-grow-1 items-center text-grey text-bold"></div>
+ </div>
+ <div class="flex flex-1 flex-col p-8 pt-2">
+ <div class="text-grey mb-6">ALL ITEMS</div>
+ <div class="items-container grid grid-cols-4 gap-8">
+ </div>
+ </div>
+ </div>
+ </section>`
+ );
+
+ this.$component = this.wrapper.find('.items-selector');
+ }
+
+ async load_items_data() {
+ if (!this.item_group) {
+ const res = await frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name");
+ this.parent_item_group = res.message.name;
+ };
+ if (!this.price_list) {
+ const res = await frappe.db.get_value("POS Profile", this.pos_profile, "selling_price_list");
+ this.price_list = res.message.selling_price_list;
+ }
+
+ this.get_items({}).then(({message}) => {
+ this.render_item_list(message.items);
+ });
+ }
+
+ get_items({start = 0, page_length = 40, search_value=''}) {
+ const price_list = this.events.get_frm().doc?.selling_price_list || this.price_list;
+ let { item_group, pos_profile } = this;
+
+ !item_group && (item_group = this.parent_item_group);
+
+ return frappe.call({
+ method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items",
+ freeze: true,
+ args: { start, page_length, price_list, item_group, search_value, pos_profile },
+ });
+ }
+
+
+ render_item_list(items) {
+ this.$items_container = this.$component.find('.items-container');
+ this.$items_container.html('');
+
+ items.forEach(item => {
+ const item_html = this.get_item_html(item);
+ this.$items_container.append(item_html);
+ })
+ }
+
+ get_item_html(item) {
+ const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
+ const indicator_color = actual_qty > 10 ? "green" : actual_qty !== 0 ? "orange" : "red";
+
+ function get_item_image_html() {
+ if (item_image) {
+ return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
+ <img class="h-full" src="${item_image}" alt="${item_image}" style="object-fit: cover;">
+ </div>`
+ } else {
+ return `<div class="flex items-center justify-center h-32 bg-light-grey text-6xl text-grey-100">
+ ${frappe.get_abbr(item.item_name)}
+ </div>`
+ }
+ }
+
+ return (
+ `<div class="item-wrapper rounded shadow pointer no-select" data-item-code="${escape(item.item_code)}"
+ data-serial-no="${escape(serial_no)}" data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
+ title="Avaiable Qty: ${actual_qty}">
+ ${get_item_image_html()}
+ <div class="flex items-center pr-4 pl-4 h-10 justify-between">
+ <div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
+ <span class="indicator ${indicator_color}"></span>
+ ${frappe.ellipsis(item.item_name, 18)}
+ </div>
+ <div class="f-shrink-0 text-dark-grey text-bold ml-4">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
+ </div>
+ </div>`
+ )
+ }
+
+ make_search_bar() {
+ const me = this;
+ this.$component.find('.search-field').html('');
+ this.$component.find('.item-group-field').html('');
+
+ this.search_field = frappe.ui.form.make_control({
+ df: {
+ label: __('Search'),
+ fieldtype: 'Data',
+ placeholder: __('Search by item code, serial number, batch no or barcode')
+ },
+ parent: this.$component.find('.search-field'),
+ render_input: true,
+ });
+ this.item_group_field = frappe.ui.form.make_control({
+ df: {
+ label: __('Item Group'),
+ fieldtype: 'Link',
+ options: 'Item Group',
+ placeholder: __('Select item group'),
+ onchange: function() {
+ me.item_group = this.value;
+ !me.item_group && (me.item_group = me.parent_item_group);
+ me.filter_items();
+ },
+ get_query: function () {
+ return {
+ query: 'erpnext.selling.page.point_of_sale.point_of_sale.item_group_query',
+ filters: {
+ pos_profile: me.events.get_frm().doc?.pos_profile
+ }
+ }
+ },
+ },
+ parent: this.$component.find('.item-group-field'),
+ render_input: true,
+ });
+ this.search_field.toggle_label(false);
+ this.item_group_field.toggle_label(false);
+ }
+
+ bind_events() {
+ const me = this;
+ onScan.attachTo(document, {
+ onScan: (sScancode) => {
+ if (this.search_field && this.$component.is(':visible')) {
+ this.search_field.set_focus();
+ $(this.search_field.$input[0]).val(sScancode).trigger("input");
+ this.barcode_scanned = true;
+ }
+ }
+ });
+
+ this.$component.on('click', '.item-wrapper', function() {
+ const $item = $(this);
+ const item_code = unescape($item.attr('data-item-code'));
+ let batch_no = unescape($item.attr('data-batch-no'));
+ let serial_no = unescape($item.attr('data-serial-no'));
+ let uom = unescape($item.attr('data-uom'));
+
+ // escape(undefined) returns "undefined" then unescape returns "undefined"
+ batch_no = batch_no === "undefined" ? undefined : batch_no;
+ serial_no = serial_no === "undefined" ? undefined : serial_no;
+ uom = uom === "undefined" ? undefined : uom;
+
+ me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }});
+ })
+
+ this.search_field.$input.on('input', (e) => {
+ clearTimeout(this.last_search);
+ this.last_search = setTimeout(() => {
+ const search_term = e.target.value;
+ this.filter_items({ search_term });
+ }, 300);
+ });
+ }
+
+ attach_shortcuts() {
+ frappe.ui.keys.on("ctrl+i", () => {
+ const selector_is_visible = this.$component.is(':visible');
+ if (!selector_is_visible) return;
+ this.search_field.set_focus();
+ });
+ frappe.ui.keys.on("ctrl+g", () => {
+ const selector_is_visible = this.$component.is(':visible');
+ if (!selector_is_visible) return;
+ this.item_group_field.set_focus();
+ });
+ // for selecting the last filtered item on search
+ frappe.ui.keys.on("enter", () => {
+ const selector_is_visible = this.$component.is(':visible');
+ if (!selector_is_visible || this.search_field.get_value() === "") return;
+
+ if (this.items.length == 1) {
+ this.$items_container.find(".item-wrapper").click();
+ frappe.utils.play_sound("submit");
+ $(this.search_field.$input[0]).val("").trigger("input");
+ } else if (this.items.length == 0 && this.barcode_scanned) {
+ // only show alert of barcode is scanned and enter is pressed
+ frappe.show_alert({
+ message: __("No items found. Scan barcode again."),
+ indicator: 'orange'
+ });
+ frappe.utils.play_sound("error");
+ this.barcode_scanned = false;
+ $(this.search_field.$input[0]).val("").trigger("input");
+ }
+ });
+ }
+
+ filter_items({ search_term='' }={}) {
+ if (search_term) {
+ search_term = search_term.toLowerCase();
+
+ // memoize
+ this.search_index = this.search_index || {};
+ if (this.search_index[search_term]) {
+ const items = this.search_index[search_term];
+ this.items = items;
+ this.render_item_list(items);
+ return;
+ }
+ }
+
+ this.get_items({ search_value: search_term })
+ .then(({ message }) => {
+ const { items, serial_no, batch_no, barcode } = message;
+ if (search_term && !barcode) {
+ this.search_index[search_term] = items;
+ }
+ this.items = items;
+ this.render_item_list(items);
+ });
+ }
+
+ resize_selector(minimize) {
+ minimize ?
+ this.$component.find('.search-field').removeClass('mr-8') :
+ this.$component.find('.search-field').addClass('mr-8');
+
+ minimize ?
+ this.$component.find('.filter-section').addClass('flex-col') :
+ this.$component.find('.filter-section').removeClass('flex-col');
+
+ minimize ?
+ this.$component.removeClass('col-span-6').addClass('col-span-2') :
+ this.$component.removeClass('col-span-2').addClass('col-span-6')
+
+ minimize ?
+ this.$items_container.removeClass('grid-cols-4').addClass('grid-cols-1') :
+ this.$items_container.removeClass('grid-cols-1').addClass('grid-cols-4')
+ }
+
+ toggle_component(show) {
+ show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
+ }
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_number_pad.js b/erpnext/selling/page/point_of_sale/pos_number_pad.js
new file mode 100644
index 0000000..2ffc2c0
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_number_pad.js
@@ -0,0 +1,49 @@
+erpnext.PointOfSale.NumberPad = class {
+ constructor({ wrapper, events, cols, keys, css_classes, fieldnames_map }) {
+ this.wrapper = wrapper;
+ this.events = events;
+ this.cols = cols;
+ this.keys = keys;
+ this.css_classes = css_classes || [];
+ this.fieldnames = fieldnames_map || {};
+
+ this.init_component();
+ }
+
+ init_component() {
+ this.prepare_dom();
+ this.bind_events();
+ }
+
+ prepare_dom() {
+ const { cols, keys, css_classes, fieldnames } = this;
+
+ function get_keys() {
+ return keys.reduce((a, row, i) => {
+ return a + row.reduce((a2, number, j) => {
+ const class_to_append = css_classes && css_classes[i] ? css_classes[i][j] : '';
+ const fieldname = fieldnames && fieldnames[number] ?
+ fieldnames[number] :
+ typeof number === 'string' ? frappe.scrub(number) : number;
+
+ return a2 + `<div class="numpad-btn pointer no-select rounded ${class_to_append}
+ flex items-center justify-center h-16 text-md border-grey border" data-button-value="${fieldname}">${number}</div>`
+ }, '')
+ }, '');
+ }
+
+ this.wrapper.html(
+ `<div class="grid grid-cols-${cols} gap-4">
+ ${get_keys()}
+ </div>`
+ )
+ }
+
+ bind_events() {
+ const me = this;
+ this.wrapper.on('click', '.numpad-btn', function() {
+ const $btn = $(this);
+ me.events.numpad_event($btn);
+ })
+ }
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js
new file mode 100644
index 0000000..9181ee8
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js
@@ -0,0 +1,130 @@
+erpnext.PointOfSale.PastOrderList = class {
+ constructor({ wrapper, events }) {
+ this.wrapper = wrapper;
+ this.events = events;
+
+ this.init_component();
+ }
+
+ init_component() {
+ this.prepare_dom();
+ this.make_filter_section();
+ this.bind_events();
+ }
+
+ prepare_dom() {
+ this.wrapper.append(
+ `<section class="col-span-4 flex flex-col shadow rounded past-order-list bg-white mx-h-70 h-100 d-none">
+ <div class="flex flex-col rounded w-full scroll-y">
+ <div class="filter-section flex flex-col p-8 pb-2 bg-white sticky z-100">
+ <div class="search-field flex items-center text-grey"></div>
+ <div class="status-field flex items-center text-grey text-bold"></div>
+ </div>
+ <div class="flex flex-1 flex-col p-8 pt-2">
+ <div class="text-grey mb-6">RECENT ORDERS</div>
+ <div class="invoices-container rounded border grid grid-cols-1"></div>
+ </div>
+ </div>
+ </section>`
+ )
+
+ this.$component = this.wrapper.find('.past-order-list');
+ this.$invoices_container = this.$component.find('.invoices-container');
+ }
+
+ bind_events() {
+ this.search_field.$input.on('input', (e) => {
+ clearTimeout(this.last_search);
+ this.last_search = setTimeout(() => {
+ const search_term = e.target.value;
+ this.refresh_list(search_term, this.status_field.get_value());
+ }, 300);
+ });
+ const me = this;
+ this.$invoices_container.on('click', '.invoice-wrapper', function() {
+ const invoice_name = unescape($(this).attr('data-invoice-name'));
+
+ me.events.open_invoice_data(invoice_name);
+ })
+ }
+
+ make_filter_section() {
+ const me = this;
+ this.search_field = frappe.ui.form.make_control({
+ df: {
+ label: __('Search'),
+ fieldtype: 'Data',
+ placeholder: __('Search by invoice id or customer name')
+ },
+ parent: this.$component.find('.search-field'),
+ render_input: true,
+ });
+ this.status_field = frappe.ui.form.make_control({
+ df: {
+ label: __('Invoice Status'),
+ fieldtype: 'Select',
+ options: `Draft\nPaid\nConsolidated\nReturn`,
+ placeholder: __('Filter by invoice status'),
+ onchange: function() {
+ me.refresh_list(me.search_field.get_value(), this.value);
+ }
+ },
+ parent: this.$component.find('.status-field'),
+ render_input: true,
+ });
+ this.search_field.toggle_label(false);
+ this.status_field.toggle_label(false);
+ this.status_field.set_value('Paid');
+ }
+
+ toggle_component(show) {
+ show ?
+ this.$component.removeClass('d-none') && this.refresh_list() :
+ this.$component.addClass('d-none');
+ }
+
+ refresh_list() {
+ frappe.dom.freeze();
+ this.events.reset_summary();
+ const search_term = this.search_field.get_value();
+ const status = this.status_field.get_value();
+
+ this.$invoices_container.html('');
+
+ return frappe.call({
+ method: "erpnext.selling.page.point_of_sale.point_of_sale.get_past_order_list",
+ freeze: true,
+ args: { search_term, status },
+ callback: (response) => {
+ frappe.dom.unfreeze();
+ response.message.forEach(invoice => {
+ const invoice_html = this.get_invoice_html(invoice);
+ this.$invoices_container.append(invoice_html);
+ });
+ }
+ });
+ }
+
+ get_invoice_html(invoice) {
+ const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma");
+ return (
+ `<div class="invoice-wrapper flex p-4 justify-between border-b-grey pointer no-select" data-invoice-name="${escape(invoice.name)}">
+ <div class="flex flex-col justify-end">
+ <div class="text-dark-grey text-bold overflow-hidden whitespace-nowrap mb-2">${invoice.name}</div>
+ <div class="flex items-center">
+ <div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
+ <svg class="mr-2" width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
+ </svg>
+ ${invoice.customer}
+ </div>
+ </div>
+ </div>
+ <div class="flex flex-col text-right">
+ <div class="f-shrink-0 text-lg text-dark-grey text-bold ml-4">${format_currency(invoice.grand_total, invoice.currency, 0) || 0}</div>
+ <div class="f-shrink-0 text-grey ml-4">${posting_datetime}</div>
+ </div>
+ </div>`
+ )
+ }
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
new file mode 100644
index 0000000..24326b2
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -0,0 +1,452 @@
+erpnext.PointOfSale.PastOrderSummary = class {
+ constructor({ wrapper, events }) {
+ this.wrapper = wrapper;
+ this.events = events;
+
+ this.init_component();
+ }
+
+ init_component() {
+ this.prepare_dom();
+ this.init_child_components();
+ this.bind_events();
+ this.attach_shortcuts();
+ }
+
+ prepare_dom() {
+ this.wrapper.append(
+ `<section class="col-span-6 flex flex-col items-center shadow rounded past-order-summary bg-white mx-h-70 h-100 d-none">
+ <div class="no-summary-placeholder flex flex-1 items-center justify-center p-16">
+ <div class="no-item-wrapper flex items-center h-18 pr-4 pl-4">
+ <div class="flex-1 text-center text-grey">Select an invoice to load summary data</div>
+ </div>
+ </div>
+ <div class="summary-wrapper d-none flex-1 w-66 text-dark-grey relative">
+ <div class="summary-container absolute flex flex-col pt-16 pb-16 pr-8 pl-8 w-full h-full"></div>
+ </div>
+ </section>`
+ )
+
+ this.$component = this.wrapper.find('.past-order-summary');
+ this.$summary_wrapper = this.$component.find('.summary-wrapper');
+ this.$summary_container = this.$component.find('.summary-container');
+ }
+
+ init_child_components() {
+ this.init_upper_section();
+ this.init_items_summary();
+ this.init_totals_summary();
+ this.init_payments_summary();
+ this.init_summary_buttons();
+ this.init_email_print_dialog();
+ }
+
+ init_upper_section() {
+ this.$summary_container.append(
+ `<div class="flex upper-section justify-between w-full h-24"></div>`
+ );
+
+ this.$upper_section = this.$summary_container.find('.upper-section');
+ }
+
+ init_items_summary() {
+ this.$summary_container.append(
+ `<div class="flex flex-col flex-1 mt-6 w-full scroll-y">
+ <div class="text-grey mb-4 sticky bg-white">ITEMS</div>
+ <div class="items-summary-container border rounded flex flex-col w-full"></div>
+ </div>`
+ )
+
+ this.$items_summary_container = this.$summary_container.find('.items-summary-container');
+ }
+
+ init_totals_summary() {
+ this.$summary_container.append(
+ `<div class="flex flex-col mt-6 w-full f-shrink-0">
+ <div class="text-grey mb-4">TOTALS</div>
+ <div class="summary-totals-container border rounded flex flex-col w-full"></div>
+ </div>`
+ )
+
+ this.$totals_summary_container = this.$summary_container.find('.summary-totals-container');
+ }
+
+ init_payments_summary() {
+ this.$summary_container.append(
+ `<div class="flex flex-col mt-6 w-full f-shrink-0">
+ <div class="text-grey mb-4">PAYMENTS</div>
+ <div class="payments-summary-container border rounded flex flex-col w-full mb-4"></div>
+ </div>`
+ )
+
+ this.$payment_summary_container = this.$summary_container.find('.payments-summary-container');
+ }
+
+ init_summary_buttons() {
+ this.$summary_container.append(
+ `<div class="summary-btns flex summary-btns justify-between w-full f-shrink-0"></div>`
+ )
+
+ this.$summary_btns = this.$summary_container.find('.summary-btns');
+ }
+
+ init_email_print_dialog() {
+ const email_dialog = new frappe.ui.Dialog({
+ title: 'Email Receipt',
+ fields: [
+ {fieldname:'email_id', fieldtype:'Data', options: 'Email', label:'Email ID'},
+ // {fieldname:'remarks', fieldtype:'Text', label:'Remarks (if any)'}
+ ],
+ primary_action: () => {
+ this.send_email();
+ },
+ primary_action_label: __('Send'),
+ });
+ this.email_dialog = email_dialog;
+
+ const print_dialog = new frappe.ui.Dialog({
+ title: 'Print Receipt',
+ fields: [
+ {fieldname:'print', fieldtype:'Data', label:'Print Preview'}
+ ],
+ primary_action: () => {
+ this.events.get_frm().print_preview.printit(true);
+ },
+ primary_action_label: __('Print'),
+ });
+ this.print_dialog = print_dialog;
+ }
+
+ get_upper_section_html(doc) {
+ const { status } = doc; let indicator_color = '';
+
+ in_list(['Paid', 'Consolidated'], status) && (indicator_color = 'green');
+ status === 'Draft' && (indicator_color = 'red');
+ status === 'Return' && (indicator_color = 'grey');
+
+ return `<div class="flex flex-col items-start justify-end pr-4">
+ <div class="text-lg text-bold pt-2">${doc.customer}</div>
+ <div class="text-grey">${this.customer_email}</div>
+ <div class="text-grey mt-auto">Sold by: ${doc.owner}</div>
+ </div>
+ <div class="flex flex-col flex-1 items-end justify-between">
+ <div class="text-2-5xl text-bold">${format_currency(doc.paid_amount, doc.currency)}</div>
+ <div class="flex justify-between">
+ <div class="text-grey mr-4">${doc.name}</div>
+ <div class="text-grey text-bold indicator ${indicator_color}">${doc.status}</div>
+ </div>
+ </div>`
+ }
+
+ get_discount_html(doc) {
+ if (doc.discount_amount) {
+ return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
+ <div class="flex f-shrink-1 items-center">
+ <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap mr-2">
+ Discount
+ </div>
+ <span class="text-grey">(${doc.additional_discount_percentage} %)</span>
+ </div>
+ <div class="flex flex-col f-shrink-0 ml-auto text-right">
+ <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.discount_amount, doc.currency)}</div>
+ </div>
+ </div>`;
+ } else {
+ return ``;
+ }
+ }
+
+ get_net_total_html(doc) {
+ return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
+ <div class="flex f-shrink-1 items-center">
+ <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
+ Net Total
+ </div>
+ </div>
+ <div class="flex flex-col f-shrink-0 ml-auto text-right">
+ <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.net_total, doc.currency)}</div>
+ </div>
+ </div>`
+ }
+
+ get_taxes_html(doc) {
+ return `<div class="total-summary-wrapper flex items-center justify-between h-12 pr-4 pl-4 border-b-grey">
+ <div class="flex">
+ <div class="text-md-0 text-dark-grey text-bold w-fit">Tax Charges</div>
+ <div class="flex ml-6 text-dark-grey">
+ ${
+ doc.taxes.map((t, i) => {
+ let margin_left = '';
+ if (i !== 0) margin_left = 'ml-2';
+ return `<span class="pl-2 pr-2 ${margin_left}">${t.description} @${t.rate}%</span>`
+ }).join('')
+ }
+ </div>
+ </div>
+ <div class="flex flex-col text-right">
+ <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.base_total_taxes_and_charges, doc.currency)}</div>
+ </div>
+ </div>`
+ }
+
+ get_grand_total_html(doc) {
+ return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
+ <div class="flex f-shrink-1 items-center">
+ <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
+ Grand Total
+ </div>
+ </div>
+ <div class="flex flex-col f-shrink-0 ml-auto text-right">
+ <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.grand_total, doc.currency)}</div>
+ </div>
+ </div>`
+ }
+
+ get_item_html(doc, item_data) {
+ return `<div class="item-summary-wrapper flex items-center h-12 pr-4 pl-4 border-b-grey pointer no-select">
+ <div class="flex w-6 h-6 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
+ <span>${item_data.qty || 0}</span>
+ </div>
+ <div class="flex flex-col f-shrink-1">
+ <div class="text-md text-dark-grey text-bold overflow-hidden whitespace-nowrap">
+ ${item_data.item_name}
+ </div>
+ </div>
+ <div class="flex f-shrink-0 ml-auto text-right">
+ ${get_rate_discount_html()}
+ </div>
+ </div>`
+
+ function get_rate_discount_html() {
+ if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) {
+ return `<span class="text-grey mr-2">(${item_data.discount_percentage}% off)</span>
+ <div class="text-md-0 text-dark-grey text-bold">${format_currency(item_data.rate, doc.currency)}</div>`
+ } else {
+ return `<div class="text-md-0 text-dark-grey text-bold">${format_currency(item_data.price_list_rate || item_data.rate, doc.currency)}</div>`
+ }
+ }
+ }
+
+ get_payment_html(doc, payment) {
+ return `<div class="payment-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
+ <div class="flex f-shrink-1 items-center">
+ <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
+ ${payment.mode_of_payment}
+ </div>
+ </div>
+ <div class="flex flex-col f-shrink-0 ml-auto text-right">
+ <div class="text-md-0 text-dark-grey text-bold">${format_currency(payment.amount, doc.currency)}</div>
+ </div>
+ </div>`
+ }
+
+ bind_events() {
+ this.$summary_container.on('click', '.return-btn', () => {
+ this.events.process_return(this.doc.name);
+ this.toggle_component(false);
+ this.$component.find('.no-summary-placeholder').removeClass('d-none');
+ this.$summary_wrapper.addClass('d-none');
+ });
+
+ this.$summary_container.on('click', '.edit-btn', () => {
+ this.events.edit_order(this.doc.name);
+ this.toggle_component(false);
+ this.$component.find('.no-summary-placeholder').removeClass('d-none');
+ this.$summary_wrapper.addClass('d-none');
+ });
+
+ this.$summary_container.on('click', '.new-btn', () => {
+ this.events.new_order();
+ this.toggle_component(false);
+ this.$component.find('.no-summary-placeholder').removeClass('d-none');
+ this.$summary_wrapper.addClass('d-none');
+ });
+
+ this.$summary_container.on('click', '.email-btn', () => {
+ this.email_dialog.fields_dict.email_id.set_value(this.customer_email);
+ this.email_dialog.show();
+ });
+
+ this.$summary_container.on('click', '.print-btn', () => {
+ // this.print_dialog.show();
+ const frm = this.events.get_frm();
+ frm.doc = this.doc;
+ frm.print_preview.printit(true);
+ });
+ }
+
+ attach_shortcuts() {
+ frappe.ui.keys.on("ctrl+p", () => {
+ const print_btn_visible = this.$summary_container.find('.print-btn').is(":visible");
+ const summary_visible = this.$component.is(":visible");
+ if (!summary_visible || !print_btn_visible) return;
+
+ this.$summary_container.find('.print-btn').click();
+ });
+ }
+
+ toggle_component(show) {
+ show ?
+ this.$component.removeClass('d-none') :
+ this.$component.addClass('d-none');
+ }
+
+ send_email() {
+ const frm = this.events.get_frm();
+ const recipients = this.email_dialog.get_values().recipients;
+ const doc = this.doc || frm.doc;
+ const print_format = frm.pos_print_format;
+
+ frappe.call({
+ method:"frappe.core.doctype.communication.email.make",
+ args: {
+ recipients: recipients,
+ subject: __(frm.meta.name) + ': ' + doc.name,
+ doctype: doc.doctype,
+ name: doc.name,
+ send_email: 1,
+ print_format,
+ sender_full_name: frappe.user.full_name(),
+ _lang : doc.language
+ },
+ callback: r => {
+ if(!r.exc) {
+ frappe.utils.play_sound("email");
+ if(r.message["emails_not_sent_to"]) {
+ frappe.msgprint(__("Email not sent to {0} (unsubscribed / disabled)",
+ [ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) );
+ } else {
+ frappe.show_alert({
+ message: __('Email sent successfully.'),
+ indicator: 'green'
+ });
+ }
+ this.email_dialog.hide();
+ } else {
+ frappe.msgprint(__("There were errors while sending email. Please try again."));
+ }
+ }
+ });
+ }
+
+ add_summary_btns(map) {
+ this.$summary_btns.html('');
+ map.forEach(m => {
+ if (m.condition) {
+ m.visible_btns.forEach(b => {
+ const class_name = b.split(' ')[0].toLowerCase();
+ this.$summary_btns.append(
+ `<div class="${class_name}-btn border rounded h-14 flex flex-1 items-center mr-4 justify-center text-md text-bold no-select pointer">
+ ${b}
+ </div>`
+ )
+ });
+ }
+ });
+ this.$summary_btns.children().last().removeClass('mr-4');
+ }
+
+ show_summary_placeholder() {
+ this.$summary_wrapper.addClass("d-none");
+ this.$component.find('.no-summary-placeholder').removeClass('d-none');
+ }
+
+ switch_to_post_submit_summary() {
+ // switch to full width view
+ this.$component.removeClass('col-span-6').addClass('col-span-10');
+ this.$summary_wrapper.removeClass('w-66').addClass('w-40');
+
+ // switch place holder with summary container
+ this.$component.find('.no-summary-placeholder').addClass('d-none');
+ this.$summary_wrapper.removeClass('d-none');
+ }
+
+ switch_to_recent_invoice_summary() {
+ // switch full width view with 60% view
+ this.$component.removeClass('col-span-10').addClass('col-span-6');
+ this.$summary_wrapper.removeClass('w-40').addClass('w-66');
+
+ // switch place holder with summary container
+ this.$component.find('.no-summary-placeholder').addClass('d-none');
+ this.$summary_wrapper.removeClass('d-none');
+ }
+
+ get_condition_btn_map(after_submission) {
+ if (after_submission)
+ return [{ condition: true, visible_btns: ['Print Receipt', 'Email Receipt', 'New Order'] }];
+
+ return [
+ { condition: this.doc.docstatus === 0, visible_btns: ['Edit Order'] },
+ { condition: !this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt', 'Return']},
+ { condition: this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt']}
+ ];
+ }
+
+ load_summary_of(doc, after_submission=false) {
+ this.$summary_wrapper.removeClass("d-none");
+
+ after_submission ?
+ this.switch_to_post_submit_summary() : this.switch_to_recent_invoice_summary();
+
+ this.doc = doc;
+
+ this.attach_basic_info(doc);
+
+ this.attach_items_info(doc);
+
+ this.attach_totals_info(doc);
+
+ this.attach_payments_info(doc);
+
+ const condition_btns_map = this.get_condition_btn_map(after_submission);
+
+ this.add_summary_btns(condition_btns_map);
+ }
+
+ attach_basic_info(doc) {
+ frappe.db.get_value('Customer', this.doc.customer, 'email_id').then(({ message }) => {
+ this.customer_email = message.email_id || '';
+ const upper_section_dom = this.get_upper_section_html(doc);
+ this.$upper_section.html(upper_section_dom);
+ });
+ }
+
+ attach_items_info(doc) {
+ this.$items_summary_container.html('');
+ doc.items.forEach(item => {
+ const item_dom = this.get_item_html(doc, item);
+ this.$items_summary_container.append(item_dom);
+ });
+ }
+
+ attach_payments_info(doc) {
+ this.$payment_summary_container.html('');
+ doc.payments.forEach(p => {
+ if (p.amount) {
+ const payment_dom = this.get_payment_html(doc, p);
+ this.$payment_summary_container.append(payment_dom);
+ }
+ });
+ if (doc.redeem_loyalty_points && doc.loyalty_amount) {
+ const payment_dom = this.get_payment_html(doc, {
+ mode_of_payment: 'Loyalty Points',
+ amount: doc.loyalty_amount,
+ });
+ this.$payment_summary_container.append(payment_dom);
+ }
+ }
+
+ attach_totals_info(doc) {
+ this.$totals_summary_container.html('');
+
+ const discount_dom = this.get_discount_html(doc);
+ const net_total_dom = this.get_net_total_html(doc);
+ const taxes_dom = this.get_taxes_html(doc);
+ const grand_total_dom = this.get_grand_total_html(doc);
+ this.$totals_summary_container.append(discount_dom);
+ this.$totals_summary_container.append(net_total_dom);
+ this.$totals_summary_container.append(taxes_dom);
+ this.$totals_summary_container.append(grand_total_dom);
+ }
+
+}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
new file mode 100644
index 0000000..e1c54f6
--- /dev/null
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -0,0 +1,503 @@
+{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %}
+
+erpnext.PointOfSale.Payment = class {
+ constructor({ events, wrapper }) {
+ this.wrapper = wrapper;
+ this.events = events;
+
+ this.init_component();
+ }
+
+ init_component() {
+ this.prepare_dom();
+ this.initialize_numpad();
+ this.bind_events();
+ this.attach_shortcuts();
+
+ }
+
+ prepare_dom() {
+ this.wrapper.append(
+ `<section class="col-span-6 flex shadow rounded payment-section bg-white mx-h-70 h-100 d-none">
+ <div class="flex flex-col p-16 pt-8 pb-8 w-full">
+ <div class="text-grey mb-6 payment-section no-select pointer">
+ PAYMENT METHOD<span class="octicon octicon-chevron-down collapse-indicator"></span>
+ </div>
+ <div class="payment-modes flex flex-wrap"></div>
+ <div class="invoice-details-section"></div>
+ <div class="flex mt-auto justify-center w-full">
+ <div class="flex flex-col justify-center flex-1 ml-4">
+ <div class="flex w-full">
+ <div class="totals-remarks items-end justify-end flex flex-1">
+ <div class="remarks text-md-0 text-grey mr-auto"></div>
+ <div class="totals flex justify-end pt-4"></div>
+ </div>
+ <div class="number-pad w-40 mb-4 ml-8 d-none"></div>
+ </div>
+ <div class="flex items-center justify-center mt-4 submit-order h-16 w-full rounded bg-primary text-md text-white no-select pointer text-bold">
+ Complete Order
+ </div>
+ <div class="order-time flex items-center justify-end mt-2 pt-2 pb-2 w-full text-md-0 text-grey no-select pointer d-none"></div>
+ </div>
+ </div>
+ </div>
+ </section>`
+ )
+ this.$component = this.wrapper.find('.payment-section');
+ this.$payment_modes = this.$component.find('.payment-modes');
+ this.$totals_remarks = this.$component.find('.totals-remarks');
+ this.$totals = this.$component.find('.totals');
+ this.$remarks = this.$component.find('.remarks');
+ this.$numpad = this.$component.find('.number-pad');
+ this.$invoice_details_section = this.$component.find('.invoice-details-section');
+ }
+
+ make_invoice_fields_control() {
+ frappe.db.get_doc("POS Settings", undefined).then((doc) => {
+ const fields = doc.invoice_fields;
+ if (!fields.length) return;
+
+ this.$invoice_details_section.html(
+ `<div class="text-grey pb-6 mt-2 pointer no-select">
+ ADDITIONAL INFORMATION<span class="octicon octicon-chevron-down collapse-indicator"></span>
+ </div>
+ <div class="invoice-fields grid grid-cols-2 gap-4 mb-6 d-none"></div>`
+ );
+ this.$invoice_fields = this.$invoice_details_section.find('.invoice-fields');
+ const frm = this.events.get_frm();
+
+ fields.forEach(df => {
+ this.$invoice_fields.append(
+ `<div class="invoice_detail_field ${df.fieldname}-field" data-fieldname="${df.fieldname}"></div>`
+ );
+
+ this[`${df.fieldname}_field`] = frappe.ui.form.make_control({
+ df: {
+ ...df,
+ onchange: function() {
+ frm.set_value(this.df.fieldname, this.value);
+ }
+ },
+ parent: this.$invoice_fields.find(`.${df.fieldname}-field`),
+ render_input: true,
+ });
+ this[`${df.fieldname}_field`].set_value(frm.doc[df.fieldname]);
+ })
+ });
+ }
+
+ initialize_numpad() {
+ const me = this;
+ this.number_pad = new erpnext.PointOfSale.NumberPad({
+ wrapper: this.$numpad,
+ events: {
+ numpad_event: function($btn) {
+ me.on_numpad_clicked($btn);
+ }
+ },
+ cols: 3,
+ keys: [
+ [ 1, 2, 3 ],
+ [ 4, 5, 6 ],
+ [ 7, 8, 9 ],
+ [ '.', 0, 'Delete' ]
+ ],
+ })
+
+ this.numpad_value = '';
+ }
+
+ on_numpad_clicked($btn) {
+ const me = this;
+ const button_value = $btn.attr('data-button-value');
+
+ highlight_numpad_btn($btn);
+ this.numpad_value = button_value === 'delete' ? this.numpad_value.slice(0, -1) : this.numpad_value + button_value;
+ this.selected_mode.$input.get(0).focus();
+ this.selected_mode.set_value(this.numpad_value);
+
+ function highlight_numpad_btn($btn) {
+ $btn.addClass('shadow-inner bg-selected');
+ setTimeout(() => {
+ $btn.removeClass('shadow-inner bg-selected');
+ }, 100);
+ }
+ }
+
+ bind_events() {
+ const me = this;
+
+ this.$payment_modes.on('click', '.mode-of-payment', function(e) {
+ const mode_clicked = $(this);
+ // if clicked element doesn't have .mode-of-payment class then return
+ if (!$(e.target).is(mode_clicked)) return;
+
+ const mode = mode_clicked.attr('data-mode');
+
+ // hide all control fields and shortcuts
+ $(`.mode-of-payment-control`).addClass('d-none');
+ $(`.cash-shortcuts`).addClass('d-none');
+ me.$payment_modes.find(`.pay-amount`).removeClass('d-none');
+ me.$payment_modes.find(`.loyalty-amount-name`).addClass('d-none');
+
+ // remove highlight from all mode-of-payments
+ $('.mode-of-payment').removeClass('border-primary');
+
+ if (mode_clicked.hasClass('border-primary')) {
+ // clicked one is selected then unselect it
+ mode_clicked.removeClass('border-primary');
+ me.selected_mode = '';
+ me.toggle_numpad(false);
+ } else {
+ // clicked one is not selected then select it
+ mode_clicked.addClass('border-primary');
+ mode_clicked.find('.mode-of-payment-control').removeClass('d-none');
+ mode_clicked.find('.cash-shortcuts').removeClass('d-none');
+ me.$payment_modes.find(`.${mode}-amount`).addClass('d-none');
+ me.$payment_modes.find(`.${mode}-name`).removeClass('d-none');
+ me.toggle_numpad(true);
+
+ me.selected_mode = me[`${mode}_control`];
+ const doc = me.events.get_frm().doc;
+ me.selected_mode?.$input?.get(0).focus();
+ !me.selected_mode?.get_value() ? me.selected_mode?.set_value(doc.grand_total - doc.paid_amount) : '';
+ }
+ })
+
+ this.$payment_modes.on('click', '.shortcut', function(e) {
+ const value = $(this).attr('data-value');
+ me.selected_mode.set_value(value);
+ })
+
+ // this.$totals_remarks.on('click', '.remarks', () => {
+ // this.toggle_remarks_control();
+ // })
+
+ this.$component.on('click', '.submit-order', () => {
+ const doc = this.events.get_frm().doc;
+ const paid_amount = doc.paid_amount;
+ const items = doc.items;
+
+ if (paid_amount == 0 || !items.length) {
+ const message = items.length ? __("You cannot submit the order without payment.") : __("You cannot submit empty order.")
+ frappe.show_alert({ message, indicator: "orange" });
+ frappe.utils.play_sound("error");
+ return;
+ }
+
+ this.events.submit_invoice();
+ })
+
+ frappe.ui.form.on('POS Invoice', 'paid_amount', (frm) => {
+ this.update_totals_section(frm.doc);
+
+ // need to re calculate cash shortcuts after discount is applied
+ const is_cash_shortcuts_invisible = this.$payment_modes.find('.cash-shortcuts').hasClass('d-none');
+ this.attach_cash_shortcuts(frm.doc);
+ !is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').removeClass('d-none');
+ })
+
+ frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => {
+ const formatted_currency = format_currency(frm.doc.loyalty_amount, frm.doc.currency);
+ this.$payment_modes.find(`.loyalty-amount-amount`).html(formatted_currency);
+ });
+
+ frappe.ui.form.on("Sales Invoice Payment", "amount", (frm, cdt, cdn) => {
+ // for setting correct amount after loyalty points are redeemed
+ const default_mop = locals[cdt][cdn];
+ const mode = default_mop.mode_of_payment.replace(' ', '_').toLowerCase();
+ if (this[`${mode}_control`] && this[`${mode}_control`].get_value() != default_mop.amount) {
+ this[`${mode}_control`].set_value(default_mop.amount);
+ }
+ });
+
+ this.$component.on('click', '.invoice-details-section', function(e) {
+ if ($(e.target).closest('.invoice-fields').length) return;
+
+ me.$payment_modes.addClass('d-none');
+ me.$invoice_fields.toggleClass("d-none");
+ me.toggle_numpad(false);
+ });
+ this.$component.on('click', '.payment-section', () => {
+ this.$invoice_fields.addClass("d-none");
+ this.$payment_modes.toggleClass('d-none');
+ this.toggle_numpad(true);
+ })
+ }
+
+ attach_shortcuts() {
+ frappe.ui.keys.on("ctrl+enter", () => {
+ const payment_is_visible = this.$component.is(":visible");
+ const active_mode = this.$payment_modes.find(".border-primary");
+ if (payment_is_visible && active_mode.length) {
+ this.$component.find('.submit-order').click();
+ }
+ });
+
+ frappe.ui.keys.on("tab", () => {
+ const payment_is_visible = this.$component.is(":visible");
+ const mode_of_payments = Array.from(this.$payment_modes.find(".mode-of-payment")).map(m => $(m).attr("data-mode"));
+ let active_mode = this.$payment_modes.find(".border-primary");
+ active_mode = active_mode.length ? active_mode.attr("data-mode") : undefined;
+
+ if (!active_mode) return;
+
+ const mode_index = mode_of_payments.indexOf(active_mode);
+ const next_mode_index = (mode_index + 1) % mode_of_payments.length;
+ const next_mode_to_be_clicked = this.$payment_modes.find(`.mode-of-payment[data-mode="${mode_of_payments[next_mode_index]}"]`);
+
+ if (payment_is_visible && mode_index != next_mode_index) {
+ next_mode_to_be_clicked.click();
+ }
+ });
+ }
+
+ toggle_numpad(show) {
+ if (show) {
+ this.$numpad.removeClass('d-none');
+ this.$remarks.addClass('d-none');
+ this.$totals_remarks.addClass('w-60 justify-center').removeClass('justify-end w-full');
+ } else {
+ this.$numpad.addClass('d-none');
+ this.$remarks.removeClass('d-none');
+ this.$totals_remarks.removeClass('w-60 justify-center').addClass('justify-end w-full');
+ }
+ }
+
+ render_payment_section() {
+ this.render_payment_mode_dom();
+ this.make_invoice_fields_control();
+ this.update_totals_section();
+ }
+
+ edit_cart() {
+ this.events.toggle_other_sections(false);
+ this.toggle_component(false);
+ }
+
+ checkout() {
+ this.events.toggle_other_sections(true);
+ this.toggle_component(true);
+
+ this.render_payment_section();
+ }
+
+ toggle_remarks_control() {
+ if (this.$remarks.find('.frappe-control').length) {
+ this.$remarks.html('+ Add Remark');
+ } else {
+ this.$remarks.html('');
+ this[`remark_control`] = frappe.ui.form.make_control({
+ df: {
+ label: __('Remark'),
+ fieldtype: 'Data',
+ onchange: function() {}
+ },
+ parent: this.$totals_remarks.find(`.remarks`),
+ render_input: true,
+ });
+ this[`remark_control`].set_value('');
+ }
+ }
+
+ render_payment_mode_dom() {
+ const doc = this.events.get_frm().doc;
+ const payments = doc.payments;
+ const currency = doc.currency;
+
+ this.$payment_modes.html(
+ `${
+ payments.map((p, i) => {
+ const mode = p.mode_of_payment.replace(' ', '_').toLowerCase();
+ const payment_type = p.type;
+ const margin = i % 2 === 0 ? 'pr-2' : 'pl-2';
+ const amount = p.amount > 0 ? format_currency(p.amount, currency) : '';
+
+ return (
+ `<div class="w-half ${margin} bg-white">
+ <div class="mode-of-payment rounded border border-grey text-grey text-md
+ mb-4 p-8 pt-4 pb-4 no-select pointer" data-mode="${mode}" data-payment-type="${payment_type}">
+ ${p.mode_of_payment}
+ <div class="${mode}-amount pay-amount inline float-right text-bold">${amount}</div>
+ <div class="${mode} mode-of-payment-control mt-4 flex flex-1 items-center d-none"></div>
+ </div>
+ </div>`
+ )
+ }).join('')
+ }`
+ )
+
+ payments.forEach(p => {
+ const mode = p.mode_of_payment.replace(' ', '_').toLowerCase();
+ const me = this;
+ this[`${mode}_control`] = frappe.ui.form.make_control({
+ df: {
+ label: __(`${p.mode_of_payment}`),
+ fieldtype: 'Currency',
+ placeholder: __(`Enter ${p.mode_of_payment} amount.`),
+ onchange: function() {
+ if (this.value || this.value == 0) {
+ frappe.model.set_value(p.doctype, p.name, 'amount', flt(this.value))
+ .then(() => me.update_totals_section());
+
+ const formatted_currency = format_currency(this.value, currency);
+ me.$payment_modes.find(`.${mode}-amount`).html(formatted_currency);
+ }
+ }
+ },
+ parent: this.$payment_modes.find(`.${mode}.mode-of-payment-control`),
+ render_input: true,
+ });
+ this[`${mode}_control`].toggle_label(false);
+ this[`${mode}_control`].set_value(p.amount);
+
+ if (p.default) {
+ setTimeout(() => {
+ this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click();
+ }, 500);
+ }
+ })
+
+ this.render_loyalty_points_payment_mode();
+
+ this.attach_cash_shortcuts(doc);
+ }
+
+ attach_cash_shortcuts(doc) {
+ const grand_total = doc.grand_total;
+ const currency = doc.currency;
+
+ const shortcuts = this.get_cash_shortcuts(flt(grand_total));
+
+ this.$payment_modes.find('.cash-shortcuts').remove();
+ this.$payment_modes.find('[data-payment-type="Cash"]').find('.mode-of-payment-control').after(
+ `<div class="cash-shortcuts grid grid-cols-3 gap-2 flex-1 text-center text-md-0 mb-2 d-none">
+ ${
+ shortcuts.map(s => {
+ return `<div class="shortcut rounded bg-light-grey text-dark-grey pt-2 pb-2 no-select pointer" data-value="${s}">
+ ${format_currency(s, currency)}
+ </div>`
+ }).join('')
+ }
+ </div>`
+ )
+ }
+
+ get_cash_shortcuts(grand_total) {
+ let steps = [1, 5, 10];
+ const digits = String(Math.round(grand_total)).length;
+
+ steps = steps.map(x => x * (10 ** (digits - 2)));
+
+ const get_nearest = (amount, x) => {
+ let nearest_x = Math.ceil((amount / x)) * x;
+ return nearest_x === amount ? nearest_x + x : nearest_x;
+ }
+
+ return steps.reduce((finalArr, x) => {
+ let nearest_x = get_nearest(grand_total, x);
+ nearest_x = finalArr.indexOf(nearest_x) != -1 ? nearest_x + x : nearest_x;
+ return [...finalArr, nearest_x];
+ }, []);
+ }
+
+ render_loyalty_points_payment_mode() {
+ const me = this;
+ const doc = this.events.get_frm().doc;
+ const { loyalty_program, loyalty_points, conversion_factor } = this.events.get_customer_details();
+
+ this.$payment_modes.find(`.mode-of-payment[data-mode="loyalty-amount"]`).parent().remove();
+
+ if (!loyalty_program) return;
+
+ let description, read_only, max_redeemable_amount;
+ if (!loyalty_points) {
+ description = __(`You don't have enough points to redeem.`);
+ read_only = true;
+ } else {
+ max_redeemable_amount = flt(flt(loyalty_points) * flt(conversion_factor), precision("loyalty_amount", doc))
+ description = __(`You can redeem upto ${format_currency(max_redeemable_amount)}.`);
+ read_only = false;
+ }
+
+ const margin = this.$payment_modes.children().length % 2 === 0 ? 'pr-2' : 'pl-2';
+ const amount = doc.loyalty_amount > 0 ? format_currency(doc.loyalty_amount, doc.currency) : '';
+ this.$payment_modes.append(
+ `<div class="w-half ${margin} bg-white">
+ <div class="mode-of-payment rounded border border-grey text-grey text-md
+ mb-4 p-8 pt-4 pb-4 no-select pointer" data-mode="loyalty-amount" data-payment-type="loyalty-amount">
+ Redeem Loyalty Points
+ <div class="loyalty-amount-amount pay-amount inline float-right text-bold">${amount}</div>
+ <div class="loyalty-amount-name inline float-right text-bold text-md-0 d-none">${loyalty_program}</div>
+ <div class="loyalty-amount mode-of-payment-control mt-4 flex flex-1 items-center d-none"></div>
+ </div>
+ </div>`
+ )
+
+ this['loyalty-amount_control'] = frappe.ui.form.make_control({
+ df: {
+ label: __('Redeem Loyalty Points'),
+ fieldtype: 'Currency',
+ placeholder: __(`Enter amount to be redeemed.`),
+ options: 'company:currency',
+ read_only,
+ onchange: async function() {
+ if (!loyalty_points) return;
+
+ if (this.value > max_redeemable_amount) {
+ frappe.show_alert({
+ message: __(`You cannot redeem more than ${format_currency(max_redeemable_amount)}.`),
+ indicator: "red"
+ });
+ frappe.utils.play_sound("submit");
+ me['loyalty-amount_control'].set_value(0);
+ return;
+ }
+ const redeem_loyalty_points = this.value > 0 ? 1 : 0;
+ await frappe.model.set_value(doc.doctype, doc.name, 'redeem_loyalty_points', redeem_loyalty_points);
+ frappe.model.set_value(doc.doctype, doc.name, 'loyalty_points', parseInt(this.value / conversion_factor));
+ },
+ description
+ },
+ parent: this.$payment_modes.find(`.loyalty-amount.mode-of-payment-control`),
+ render_input: true,
+ });
+ this['loyalty-amount_control'].toggle_label(false);
+
+ // this.render_add_payment_method_dom();
+ }
+
+ render_add_payment_method_dom() {
+ const docstatus = this.events.get_frm().doc.docstatus;
+ if (docstatus === 0)
+ this.$payment_modes.append(
+ `<div class="w-full pr-2">
+ <div class="add-mode-of-payment w-half text-grey mb-4 no-select pointer">+ Add Payment Method</div>
+ </div>`
+ )
+ }
+
+ update_totals_section(doc) {
+ if (!doc) doc = this.events.get_frm().doc;
+ const paid_amount = doc.paid_amount;
+ const remaining = doc.grand_total - doc.paid_amount;
+ const change = doc.change_amount || remaining <= 0 ? -1 * remaining : undefined;
+ const currency = doc.currency
+ const label = change ? __('Change') : __('To Be Paid');
+
+ this.$totals.html(
+ `<div>
+ <div class="pr-8 border-r-grey">Paid Amount</div>
+ <div class="pr-8 border-r-grey text-bold text-2xl">${format_currency(paid_amount, currency)}</div>
+ </div>
+ <div>
+ <div class="pl-8">${label}</div>
+ <div class="pl-8 text-green-400 text-bold text-2xl">${format_currency(change || remaining, currency)}</div>
+ </div>`
+ )
+ }
+
+ toggle_component(show) {
+ show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
+ }
+ }
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js b/erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js
deleted file mode 100644
index 79d1700..0000000
--- a/erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js
+++ /dev/null
@@ -1,38 +0,0 @@
-QUnit.test("test:Point of Sales", function(assert) {
- assert.expect(1);
- let done = assert.async();
-
- frappe.run_serially([
- () => frappe.set_route('point-of-sale'),
- () => frappe.timeout(3),
- () => frappe.set_control('customer', 'Test Customer 1'),
- () => frappe.timeout(0.2),
- () => cur_frm.set_value('customer', 'Test Customer 1'),
- () => frappe.timeout(2),
- () => frappe.click_link('Test Product 2'),
- () => frappe.timeout(0.2),
- () => frappe.click_element(`.cart-items [data-item-code="Test Product 2"]`),
- () => frappe.timeout(0.2),
- () => frappe.click_element(`.number-pad [data-value="Rate"]`),
- () => frappe.timeout(0.2),
- () => frappe.click_element(`.number-pad [data-value="2"]`),
- () => frappe.timeout(0.2),
- () => frappe.click_element(`.number-pad [data-value="5"]`),
- () => frappe.timeout(0.2),
- () => frappe.click_element(`.number-pad [data-value="0"]`),
- () => frappe.timeout(0.2),
- () => frappe.click_element(`.number-pad [data-value="Pay"]`),
- () => frappe.timeout(0.2),
- () => frappe.click_element(`.frappe-control [data-value="4"]`),
- () => frappe.timeout(0.2),
- () => frappe.click_element(`.frappe-control [data-value="5"]`),
- () => frappe.timeout(0.2),
- () => frappe.click_element(`.frappe-control [data-value="0"]`),
- () => frappe.timeout(0.2),
- () => frappe.click_button('Submit'),
- () => frappe.click_button('Yes'),
- () => frappe.timeout(3),
- () => assert.ok(cur_frm.doc.docstatus==1, "Sales invoice created successfully"),
- () => done()
- ]);
-});
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher/__init__.py b/erpnext/selling/print_format/__init__.py
similarity index 100%
rename from erpnext/selling/doctype/pos_closing_voucher/__init__.py
rename to erpnext/selling/print_format/__init__.py
diff --git a/erpnext/selling/doctype/pos_closing_voucher/__init__.py b/erpnext/selling/print_format/gst_pos_invoice/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/pos_closing_voucher/__init__.py
copy to erpnext/selling/print_format/gst_pos_invoice/__init__.py
diff --git a/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json
new file mode 100644
index 0000000..9094a07b
--- /dev/null
+++ b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json
@@ -0,0 +1,23 @@
+{
+ "align_labels_right": 0,
+ "creation": "2017-08-08 12:33:04.773099",
+ "custom_format": 1,
+ "disabled": 0,
+ "doc_type": "POS Invoice",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\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<br>{{ 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<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ 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<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "idx": 0,
+ "line_breaks": 0,
+ "modified": "2020-04-29 16:47:02.743246",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "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/selling/doctype/pos_closing_voucher/__init__.py b/erpnext/selling/print_format/pos_invoice/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/pos_closing_voucher/__init__.py
copy to erpnext/selling/print_format/pos_invoice/__init__.py
diff --git a/erpnext/selling/print_format/pos_invoice/pos_invoice.json b/erpnext/selling/print_format/pos_invoice/pos_invoice.json
new file mode 100644
index 0000000..99094ed
--- /dev/null
+++ b/erpnext/selling/print_format/pos_invoice/pos_invoice.json
@@ -0,0 +1,22 @@
+{
+ "align_labels_right": 0,
+ "creation": "2011-12-21 11:08:55",
+ "custom_format": 1,
+ "disabled": 0,
+ "doc_type": "POS Invoice",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\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<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "idx": 1,
+ "line_breaks": 0,
+ "modified": "2020-04-29 16:45:58.942375",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "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/selling/doctype/pos_closing_voucher/__init__.py b/erpnext/selling/print_format/return_pos_invoice/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/pos_closing_voucher/__init__.py
copy to erpnext/selling/print_format/return_pos_invoice/__init__.py
diff --git a/erpnext/selling/print_format/return_pos_invoice/return_pos_invoice.json b/erpnext/selling/print_format/return_pos_invoice/return_pos_invoice.json
new file mode 100644
index 0000000..d7f3350
--- /dev/null
+++ b/erpnext/selling/print_format/return_pos_invoice/return_pos_invoice.json
@@ -0,0 +1,24 @@
+{
+ "align_labels_right": 0,
+ "creation": "2020-05-14 17:02:44.207166",
+ "custom_format": 1,
+ "default_print_language": "en",
+ "disabled": 0,
+ "doc_type": "POS Invoice",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n<p class=\"text-center\" style=\"margin-bottom: 1rem\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Return Invoice\") }}<br>\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Original Invoice\") }}:</b> {{ doc.return_against }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t<b>{{ _(\"Customer\") }}:</b> {{ doc.customer_name }}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\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<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"SR.No\") }}:</b><br>\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.get_formatted(\"rate\") }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc)}}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.change_amount -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\")}}\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t{%- endif -%}\n\t</tbody>\n</table>\n<hr>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
+ "idx": 0,
+ "line_breaks": 0,
+ "modified": "2020-05-14 17:13:29.354015",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Return 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/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index bd59be6..0a70b97 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -9,6 +9,9 @@
def execute(filters=None):
filters = frappe._dict(filters or {})
+ if filters.from_date > filters.to_date:
+ frappe.throw(_('From Date cannot be greater than To Date'))
+
columns = get_columns(filters)
data = get_data(filters)
@@ -188,7 +191,7 @@
conditions += "AND so_item.item_code = '%s'" %frappe.db.escape(filters.item_code)
if filters.get("customer"):
- conditions += "AND so.customer = '%s'" %frappe.db.escape(filters.customer)
+ conditions += "AND so.customer = %s" %frappe.db.escape(filters.customer)
return conditions
diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py
index 14d8031..e89c451 100644
--- a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py
+++ b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py
@@ -158,7 +158,7 @@
}
pending_so.append(so_record)
else:
- for item in bundled_item_map.get((so.name, so.item_code)):
+ for item in bundled_item_map.get((so.name, so.item_code), []):
material_requests_against_so = materials_request_dict.get((so.name, item.item_code)) or {}
if flt(item.qty) > flt(material_requests_against_so.get('qty')):
so_record = {
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index 7e8e6e9..f5feb95 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -96,7 +96,7 @@
# prepare data for report view
row["qty_to_bill"] = flt(row["qty"]) - flt(row["billed_qty"])
- row["delay"] = 0 if row["delay"] < 0 else row["delay"]
+ row["delay"] = 0 if row["delay"] and row["delay"] < 0 else row["delay"]
if filters.get("group_by_so"):
so_name = row["sales_order"]
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 4a7dd5a..333a563 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -142,7 +142,7 @@
frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]);
// check if child doctype is Sales Order Item/Qutation Item and calculate the rate
- if(in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item"]), cdt)
+ if(in_list(["Quotation Item", "Sales Order Item", "Delivery Note Item", "Sales Invoice Item", "POS Invoice Item"]), cdt)
this.apply_pricing_rule_on_item(item);
else
item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0),
@@ -312,6 +312,11 @@
batch_no: function(doc, cdt, cdn) {
var me = this;
var item = frappe.get_doc(cdt, cdn);
+
+ if (item.serial_no) {
+ return;
+ }
+
item.serial_no = null;
var has_serial_no;
frappe.db.get_value('Item', {'item_code': item.item_code}, 'has_serial_no', (r) => {
diff --git a/erpnext/selling/selling_dashboard/selling/selling.json b/erpnext/selling/selling_dashboard/selling/selling.json
new file mode 100644
index 0000000..52e6714
--- /dev/null
+++ b/erpnext/selling/selling_dashboard/selling/selling.json
@@ -0,0 +1,46 @@
+{
+ "cards": [
+ {
+ "card": "Annual Sales"
+ },
+ {
+ "card": "Sales Orders to Deliver"
+ },
+ {
+ "card": "Sales Orders to Bill"
+ },
+ {
+ "card": "Active Customers"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "Sales Order Trends",
+ "width": "Full"
+ },
+ {
+ "chart": "Top Customers",
+ "width": "Half"
+ },
+ {
+ "chart": "Sales Order Analysis",
+ "width": "Half"
+ },
+ {
+ "chart": "Item-wise Annual Sales",
+ "width": "Full"
+ }
+ ],
+ "creation": "2020-07-20 20:17:16.688162",
+ "dashboard_name": "Selling",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 0,
+ "is_standard": 1,
+ "modified": "2020-07-22 15:31:22.299903",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Selling",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index c25edc5..221044d 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -22,7 +22,6 @@
"default_letter_head",
"default_holiday_list",
"default_finance_book",
- "standard_working_hours",
"default_selling_terms",
"default_buying_terms",
"default_warehouse_for_sales_return",
@@ -241,11 +240,6 @@
"options": "Holiday List"
},
{
- "fieldname": "standard_working_hours",
- "fieldtype": "Float",
- "label": "Standard Working Hours"
- },
- {
"fieldname": "default_warehouse_for_sales_return",
"fieldtype": "Link",
"label": "Default warehouse for Sales Return",
@@ -746,7 +740,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
- "modified": "2020-06-20 11:38:43.178970",
+ "modified": "2020-06-24 12:45:31.462195",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index 7c0be3b..b30bd78 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -405,8 +405,8 @@
value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_billed/100)),0),
count(*) from `tabSales Order`
- where (transaction_date <= %(to_date)s) and billing_status != "Fully Billed"
- and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0]
+ where (transaction_date <= %(to_date)s) and billing_status != "Fully Billed" and company = %(company)s
+ and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0]
label = get_link_to_report('Sales Order', label=self.meta.get_label("sales_orders_to_bill"),
report_type="Report Builder",
@@ -430,8 +430,8 @@
value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_delivered/100)),0),
count(*) from `tabSales Order`
- where (transaction_date <= %(to_date)s) and delivery_status != "Fully Delivered"
- and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0]
+ where (transaction_date <= %(to_date)s) and delivery_status != "Fully Delivered" and company = %(company)s
+ and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0]
label = get_link_to_report('Sales Order', label=self.meta.get_label("sales_orders_to_deliver"),
report_type="Report Builder",
@@ -455,8 +455,8 @@
value, count = frappe.db.sql("""select ifnull((sum(grand_total))-(sum(grand_total*per_received/100)),0),
count(*) from `tabPurchase Order`
- where (transaction_date <= %(to_date)s) and per_received < 100
- and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0]
+ where (transaction_date <= %(to_date)s) and per_received < 100 and company = %(company)s
+ and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0]
label = get_link_to_report('Purchase Order', label=self.meta.get_label("purchase_orders_to_receive"),
report_type="Report Builder",
@@ -480,8 +480,8 @@
value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_billed/100)),0),
count(*) from `tabPurchase Order`
- where (transaction_date <= %(to_date)s) and per_billed < 100
- and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0]
+ where (transaction_date <= %(to_date)s) and per_billed < 100 and company = %(company)s
+ and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0]
label = get_link_to_report('Purchase Order', label=self.meta.get_label("purchase_orders_to_bill"),
report_type="Report Builder",
diff --git a/erpnext/setup/doctype/party_type/party_type.py b/erpnext/setup/doctype/party_type/party_type.py
index b29c305..96e6093 100644
--- a/erpnext/setup/doctype/party_type/party_type.py
+++ b/erpnext/setup/doctype/party_type/party_type.py
@@ -10,6 +10,7 @@
pass
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_party_type(doctype, txt, searchfield, start, page_len, filters):
cond = ''
if filters and filters.get('account'):
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 74ff0ec..aa9fbc0 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -29,12 +29,10 @@
def check_setup_wizard_not_completed():
- if frappe.db.get_default('desktop:home_page') == 'desktop':
- print()
- print("ERPNext can only be installed on a fresh site where the setup wizard is not completed")
- print("You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall")
- print()
- return False
+ if frappe.db.get_default('desktop:home_page') != 'setup-wizard':
+ message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed.
+You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall"""
+ frappe.throw(message)
def set_single_defaults():
@@ -105,4 +103,3 @@
"ref_doctype": "Company"
})
settings.save()
-
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
index e1510f5..21fa4c3 100644
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
+++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
@@ -1,25 +1,22 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-$.extend(cur_frm.cscript, {
- onload: function() {
- if(cur_frm.doc.__onload && cur_frm.doc.__onload.quotation_series) {
- cur_frm.fields_dict.quotation_series.df.options = cur_frm.doc.__onload.quotation_series;
- cur_frm.refresh_field("quotation_series");
+frappe.ui.form.on("Shopping Cart Settings", {
+ onload: function(frm) {
+ if(frm.doc.__onload && frm.doc.__onload.quotation_series) {
+ frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series;
+ frm.refresh_field("quotation_series");
}
},
- refresh: function(){
- toggle_mandatory(cur_frm)
- },
- enable_checkout: function(){
- toggle_mandatory(cur_frm)
+ enabled: function(frm) {
+ if (frm.doc.enabled === 1) {
+ frm.set_value('enable_variants', 1);
+ }
+ else {
+ frm.set_value('company', '');
+ frm.set_value('price_list', '');
+ frm.set_value('default_customer_group', '');
+ frm.set_value('quotation_series', '');
+ }
}
});
-
-
-function toggle_mandatory (cur_frm){
- cur_frm.toggle_reqd("payment_gateway_account", false);
- if(cur_frm.doc.enabled && cur_frm.doc.enable_checkout) {
- cur_frm.toggle_reqd("payment_gateway_account", true);
- }
-}
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
index e828f54..271895f 100644
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
+++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
@@ -1,750 +1,193 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2013-06-19 15:57:32",
- "custom": 0,
- "description": "Default settings for Shopping Cart",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "System",
- "editable_grid": 0,
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "enabled",
- "fieldtype": "Check",
- "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": "Enable Shopping Cart",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "description": "",
- "fieldname": "display_settings",
- "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": "Display Settings",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "description": "",
- "fieldname": "show_attachments",
- "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": "Show Public Attachments",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "description": "",
- "fieldname": "show_price",
- "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": "Show Price",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "show_stock_availability",
- "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": "Show Stock Availability",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "show_configure_button",
- "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": "Show Configure Button",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "show_contact_us_button",
- "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": "Show Contact Us Button",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "show_stock_availability",
- "fieldname": "show_quantity_in_website",
- "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": "Show Stock Quantity",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fetch_if_empty": 0,
- "fieldname": "show_apply_coupon_code_in_website",
- "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": "Show Apply Coupon 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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "allow_items_not_in_stock",
- "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": "Allow items not in stock to be added to cart",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "enabled",
- "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,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "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,
- "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,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Prices will not be shown if Price List is not set",
- "fieldname": "price_list",
- "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": "Price List",
- "length": 0,
- "no_copy": 0,
- "options": "Price List",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "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,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "default_customer_group",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Default Customer Group",
- "length": 0,
- "no_copy": 0,
- "options": "Customer Group",
- "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,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "quotation_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": "Quotation Series",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "collapsible_depends_on": "eval:doc.enable_checkout",
- "columns": 0,
- "depends_on": "enabled",
- "fieldname": "section_break_8",
- "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": "Checkout Settings",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "collapsible_depends_on": "",
- "columns": 0,
- "depends_on": "",
- "fieldname": "enable_checkout",
- "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": "Enable Checkout",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Orders",
- "description": "After payment completion redirect user to selected page.",
- "fieldname": "payment_success_url",
- "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": "Payment Success Url",
- "length": 0,
- "no_copy": 0,
- "options": "\nOrders\nInvoices\nMy 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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_11",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "payment_gateway_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": "Payment Gateway Account",
- "length": 0,
- "no_copy": 0,
- "options": "Payment Gateway 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
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-shopping-cart",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-10-14 13:54:24.575322",
- "modified_by": "Administrator",
- "module": "Shopping Cart",
- "name": "Shopping Cart Settings",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Website Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
- }
\ No newline at end of file
+ "actions": [],
+ "creation": "2013-06-19 15:57:32",
+ "description": "Default settings for Shopping Cart",
+ "doctype": "DocType",
+ "document_type": "System",
+ "engine": "InnoDB",
+ "field_order": [
+ "enabled",
+ "display_settings",
+ "show_attachments",
+ "show_price",
+ "show_stock_availability",
+ "enable_variants",
+ "column_break_7",
+ "show_contact_us_button",
+ "show_quantity_in_website",
+ "show_apply_coupon_code_in_website",
+ "allow_items_not_in_stock",
+ "section_break_2",
+ "company",
+ "price_list",
+ "column_break_4",
+ "default_customer_group",
+ "quotation_series",
+ "section_break_8",
+ "enable_checkout",
+ "payment_success_url",
+ "column_break_11",
+ "payment_gateway_account"
+ ],
+ "fields": [
+ {
+ "default": "0",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Enable Shopping Cart"
+ },
+ {
+ "fieldname": "display_settings",
+ "fieldtype": "Section Break",
+ "label": "Display Settings"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_attachments",
+ "fieldtype": "Check",
+ "label": "Show Public Attachments"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_price",
+ "fieldtype": "Check",
+ "label": "Show Price"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_stock_availability",
+ "fieldtype": "Check",
+ "label": "Show Stock Availability"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_contact_us_button",
+ "fieldtype": "Check",
+ "label": "Show Contact Us Button"
+ },
+ {
+ "default": "0",
+ "depends_on": "show_stock_availability",
+ "fieldname": "show_quantity_in_website",
+ "fieldtype": "Check",
+ "label": "Show Stock Quantity"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_apply_coupon_code_in_website",
+ "fieldtype": "Check",
+ "label": "Show Apply Coupon Code"
+ },
+ {
+ "default": "0",
+ "fieldname": "allow_items_not_in_stock",
+ "fieldtype": "Check",
+ "label": "Allow items not in stock to be added to cart"
+ },
+ {
+ "depends_on": "enabled",
+ "fieldname": "section_break_2",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "mandatory_depends_on": "eval: doc.enabled === 1",
+ "options": "Company",
+ "remember_last_selected_value": 1
+ },
+ {
+ "description": "Prices will not be shown if Price List is not set",
+ "fieldname": "price_list",
+ "fieldtype": "Link",
+ "label": "Price List",
+ "mandatory_depends_on": "eval: doc.enabled === 1",
+ "options": "Price List"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "default_customer_group",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "label": "Default Customer Group",
+ "mandatory_depends_on": "eval: doc.enabled === 1",
+ "options": "Customer Group"
+ },
+ {
+ "fieldname": "quotation_series",
+ "fieldtype": "Select",
+ "label": "Quotation Series",
+ "mandatory_depends_on": "eval: doc.enabled === 1"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:doc.enable_checkout",
+ "depends_on": "enabled",
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "label": "Checkout Settings"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_checkout",
+ "fieldtype": "Check",
+ "label": "Enable Checkout"
+ },
+ {
+ "default": "Orders",
+ "description": "After payment completion redirect user to selected page.",
+ "fieldname": "payment_success_url",
+ "fieldtype": "Select",
+ "label": "Payment Success Url",
+ "options": "\nOrders\nInvoices\nMy Account"
+ },
+ {
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "payment_gateway_account",
+ "fieldtype": "Link",
+ "label": "Payment Gateway Account",
+ "options": "Payment Gateway Account"
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_variants",
+ "fieldtype": "Check",
+ "label": "Enable Variants"
+ }
+ ],
+ "icon": "fa fa-shopping-cart",
+ "idx": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2020-08-03 19:32:27.958221",
+ "modified_by": "Administrator",
+ "module": "Shopping Cart",
+ "name": "Shopping Cart Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Website Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC"
+}
\ No newline at end of file
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
index 3098190..c069b90 100644
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
+++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
@@ -80,6 +80,7 @@
return frappe.local.shopping_cart_settings
+@frappe.whitelist(allow_guest=True)
def is_cart_enabled():
return get_shopping_cart_settings().enabled
diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py
index 7c08f5b..29617a8 100644
--- a/erpnext/shopping_cart/product_info.py
+++ b/erpnext/shopping_cart/product_info.py
@@ -55,7 +55,7 @@
def set_product_info_for_website(item):
"""set product price uom for website"""
- product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True)
+ product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get("product_info")
if product_info:
item.update(product_info)
diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py
index 90ecd46..5545f13 100644
--- a/erpnext/startup/leaderboard.py
+++ b/erpnext/startup/leaderboard.py
@@ -50,11 +50,12 @@
return leaderboards
@frappe.whitelist()
-def get_all_customers(from_date, company, field, limit = None):
+def get_all_customers(date_range, company, field, limit = None):
if field == "outstanding_amount":
filters = [['docstatus', '=', '1'], ['company', '=', company]]
- if from_date:
- filters.append(['posting_date', '>=', from_date])
+ if date_range:
+ date_range = frappe.parse_json(date_range)
+ filters.append(['posting_date', '>=', 'between', [date_range[0], date_range[1]]])
return frappe.db.get_all('Sales Invoice',
fields = ['customer as name', 'sum(outstanding_amount) as value'],
filters = filters,
@@ -68,18 +69,20 @@
elif field == "total_qty_sold":
select_field = "sum(so_item.stock_qty)"
+ date_condition = get_date_condition(date_range, 'so.transaction_date')
+
return frappe.db.sql("""
select so.customer as name, {0} as value
FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item
ON so.name = so_item.parent
- where so.docstatus = 1 and so.transaction_date >= %s and so.company = %s
+ where so.docstatus = 1 {1} and so.company = %s
group by so.customer
order by value DESC
limit %s
- """.format(select_field), (from_date, company, cint(limit)), as_dict=1) #nosec
+ """.format(select_field, date_condition), (company, cint(limit)), as_dict=1)
@frappe.whitelist()
-def get_all_items(from_date, company, field, limit = None):
+def get_all_items(date_range, company, field, limit = None):
if field in ("available_stock_qty", "available_stock_value"):
select_field = "sum(actual_qty)" if field=="available_stock_qty" else "sum(stock_value)"
return frappe.db.get_all('Bin',
@@ -102,23 +105,25 @@
select_field = "sum(order_item.stock_qty)"
select_doctype = "Purchase Order"
+ date_condition = get_date_condition(date_range, 'sales_order.transaction_date')
+
return frappe.db.sql("""
select order_item.item_code as name, {0} as value
from `tab{1}` sales_order join `tab{1} Item` as order_item
on sales_order.name = order_item.parent
where sales_order.docstatus = 1
- and sales_order.company = %s and sales_order.transaction_date >= %s
+ and sales_order.company = %s {2}
group by order_item.item_code
order by value desc
limit %s
- """.format(select_field, select_doctype), (company, from_date, cint(limit)), as_dict=1) #nosec
+ """.format(select_field, select_doctype, date_condition), (company, cint(limit)), as_dict=1) #nosec
@frappe.whitelist()
-def get_all_suppliers(from_date, company, field, limit = None):
+def get_all_suppliers(date_range, company, field, limit = None):
if field == "outstanding_amount":
filters = [['docstatus', '=', '1'], ['company', '=', company]]
- if from_date:
- filters.append(['posting_date', '>=', from_date])
+ if date_range:
+ filters.append(['posting_date', 'between' [date_range[0], date_range[1]]])
return frappe.db.get_all('Purchase Invoice',
fields = ['supplier as name', 'sum(outstanding_amount) as value'],
filters = filters,
@@ -132,18 +137,22 @@
elif field == "total_qty_purchased":
select_field = "sum(purchase_order_item.stock_qty)"
+ date_condition = get_date_condition(date_range, 'purchase_order.modified')
+
return frappe.db.sql("""
select purchase_order.supplier as name, {0} as value
FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item`
as purchase_order_item ON purchase_order.name = purchase_order_item.parent
- where purchase_order.docstatus = 1 and purchase_order.modified >= %s
+ where
+ purchase_order.docstatus = 1
+ {1}
and purchase_order.company = %s
group by purchase_order.supplier
order by value DESC
- limit %s""".format(select_field), (from_date, company, cint(limit)), as_dict=1) #nosec
+ limit %s""".format(select_field, date_condition), (company, cint(limit)), as_dict=1) #nosec
@frappe.whitelist()
-def get_all_sales_partner(from_date, company, field, limit = None):
+def get_all_sales_partner(date_range, company, field, limit = None):
if field == "total_sales_amount":
select_field = "sum(`base_net_total`)"
elif field == "total_commission":
@@ -154,8 +163,9 @@
'docstatus': 1,
'company': company
}
- if from_date:
- filters['transaction_date'] = ['>=', from_date]
+ if date_range:
+ date_range = frappe.parse_json(date_range)
+ filters['transaction_date'] = ['between', [date_range[0], date_range[1]]]
return frappe.get_list('Sales Order', fields=[
'`sales_partner` as name',
@@ -163,15 +173,27 @@
], filters=filters, group_by='sales_partner', order_by='value DESC', limit=limit)
@frappe.whitelist()
-def get_all_sales_person(from_date, company, field = None, limit = 0):
+def get_all_sales_person(date_range, company, field = None, limit = 0):
+ date_condition = get_date_condition(date_range, 'sales_order.transaction_date')
+
return frappe.db.sql("""
select sales_team.sales_person as name, sum(sales_order.base_net_total) as value
from `tabSales Order` as sales_order join `tabSales Team` as sales_team
on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order'
where sales_order.docstatus = 1
- and sales_order.transaction_date >= %s
and sales_order.company = %s
+ {date_condition}
group by sales_team.sales_person
order by value DESC
limit %s
- """, (from_date, company, cint(limit)), as_dict=1)
+ """.format(date_condition=date_condition), (company, cint(limit)), as_dict=1)
+
+def get_date_condition(date_range, field):
+ date_condition = ''
+ if date_range:
+ date_range = frappe.parse_json(date_range)
+ from_date, to_date = date_range
+ date_condition = "and {0} between {1} and {2}".format(
+ field, frappe.db.escape(from_date), frappe.db.escape(to_date)
+ )
+ return date_condition
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart/delivery_trends/delivery_trends.json b/erpnext/stock/dashboard_chart/delivery_trends/delivery_trends.json
new file mode 100644
index 0000000..b3f6e35
--- /dev/null
+++ b/erpnext/stock/dashboard_chart/delivery_trends/delivery_trends.json
@@ -0,0 +1,27 @@
+{
+ "based_on": "posting_date",
+ "chart_name": "Delivery Trends",
+ "chart_type": "Sum",
+ "color": "#4d4da8",
+ "creation": "2020-07-20 21:01:04.255291",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Delivery Note",
+ "filters_json": "[[\"Delivery Note\",\"docstatus\",\"=\",1]]",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 13:03:24.937045",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Delivery Trends",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 1,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "value_based_on": "base_net_total",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart/item_shortage_summary/item_shortage_summary.json b/erpnext/stock/dashboard_chart/item_shortage_summary/item_shortage_summary.json
new file mode 100644
index 0000000..ce71124
--- /dev/null
+++ b/erpnext/stock/dashboard_chart/item_shortage_summary/item_shortage_summary.json
@@ -0,0 +1,23 @@
+{
+ "chart_name": "Item Shortage Summary",
+ "chart_type": "Report",
+ "creation": "2020-07-20 21:01:04.383451",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}",
+ "filters_json": "{}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 13:07:01.905334",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Item Shortage Summary",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Item Shortage Report",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json b/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json
new file mode 100644
index 0000000..9c10a53
--- /dev/null
+++ b/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json
@@ -0,0 +1,24 @@
+{
+ "chart_name": "Oldest Items",
+ "chart_type": "Report",
+ "creation": "2020-07-20 21:01:04.336845",
+ "custom_options": "{\"colors\": [\"#5e64ff\"]}",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"to_date\":\"frappe.datetime.nowdate()\"}",
+ "filters_json": "{\"range1\":30,\"range2\":60,\"range3\":90,\"show_warehouse_wise_stock\":0}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-29 14:50:26.846482",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Oldest Items",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "report_name": "Stock Ageing",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 1,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart/purchase_receipt_trends/purchase_receipt_trends.json b/erpnext/stock/dashboard_chart/purchase_receipt_trends/purchase_receipt_trends.json
new file mode 100644
index 0000000..584a6cc
--- /dev/null
+++ b/erpnext/stock/dashboard_chart/purchase_receipt_trends/purchase_receipt_trends.json
@@ -0,0 +1,27 @@
+{
+ "based_on": "posting_date",
+ "chart_name": "Purchase Receipt Trends",
+ "chart_type": "Sum",
+ "color": "#78d6ff",
+ "creation": "2020-07-20 21:01:04.205230",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "document_type": "Purchase Receipt",
+ "filters_json": "[[\"Purchase Receipt\",\"docstatus\",\"=\",1]]",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 13:05:25.923130",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Purchase Receipt Trends",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "time_interval": "Monthly",
+ "timeseries": 1,
+ "timespan": "Last Year",
+ "type": "Bar",
+ "use_report_chart": 0,
+ "value_based_on": "base_net_total",
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart/warehouse_wise_stock_value/warehouse_wise_stock_value.json b/erpnext/stock/dashboard_chart/warehouse_wise_stock_value/warehouse_wise_stock_value.json
new file mode 100644
index 0000000..a07b553
--- /dev/null
+++ b/erpnext/stock/dashboard_chart/warehouse_wise_stock_value/warehouse_wise_stock_value.json
@@ -0,0 +1,22 @@
+{
+ "chart_name": "Warehouse wise Stock Value",
+ "chart_type": "Custom",
+ "creation": "2020-07-20 21:01:04.296157",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart",
+ "filters_json": "{}",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 13:01:01.815123",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Warehouse wise Stock Value",
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "source": "Warehouse wise Stock Value",
+ "timeseries": 0,
+ "type": "Bar",
+ "use_report_chart": 0,
+ "y_axis": []
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_fixtures.py b/erpnext/stock/dashboard_fixtures.py
deleted file mode 100644
index 7625b1a..0000000
--- a/erpnext/stock/dashboard_fixtures.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-import json
-from frappe import _
-from frappe.utils import nowdate
-from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
-from erpnext.buying.dashboard_fixtures import get_company_for_dashboards
-
-def get_data():
- fiscal_year = _get_fiscal_year(nowdate())
-
- if not fiscal_year:
- return frappe._dict()
-
- company = frappe.get_doc("Company", get_company_for_dashboards())
- fiscal_year_name = fiscal_year.get("name")
- start_date = str(fiscal_year.get("year_start_date"))
- end_date = str(fiscal_year.get("year_end_date"))
-
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(company, fiscal_year_name, start_date, end_date),
- "number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date),
- })
-
-def get_dashboards():
- return [{
- "name": "Stock",
- "dashboard_name": "Stock",
- "charts": [
- { "chart": "Warehouse wise Stock Value", "width": "Full"},
- { "chart": "Purchase Receipt Trends", "width": "Half"},
- { "chart": "Delivery Trends", "width": "Half"},
- { "chart": "Oldest Items", "width": "Half"},
- { "chart": "Item Shortage Summary", "width": "Half"}
- ],
- "cards": [
- { "card": "Total Active Items"},
- { "card": "Total Warehouses"},
- { "card": "Total Stock Value"}
- ]
- }]
-
-def get_charts(company, fiscal_year_name, start_date, end_date):
- return [
- {
- "doctype": "Dashboard Chart",
- "name": "Purchase Receipt Trends",
- "time_interval": "Monthly",
- "chart_name": _("Purchase Receipt Trends"),
- "timespan": "Last Year",
- "color": "#7b933d",
- "value_based_on": "base_net_total",
- "filters_json": json.dumps([["Purchase Receipt", "docstatus", "=", 1]]),
- "chart_type": "Sum",
- "timeseries": 1,
- "based_on": "posting_date",
- "owner": "Administrator",
- "document_type": "Purchase Receipt",
- "type": "Bar",
- "width": "Half",
- "is_public": 1
- },
- {
- "doctype": "Dashboard Chart",
- "name": "Delivery Trends",
- "time_interval": "Monthly",
- "chart_name": _("Delivery Trends"),
- "timespan": "Last Year",
- "color": "#7b933d",
- "value_based_on": "base_net_total",
- "filters_json": json.dumps([["Delivery Note", "docstatus", "=", 1]]),
- "chart_type": "Sum",
- "timeseries": 1,
- "based_on": "posting_date",
- "owner": "Administrator",
- "document_type": "Delivery Note",
- "type": "Bar",
- "width": "Half",
- "is_public": 1
- },
- {
- "name": "Warehouse wise Stock Value",
- "chart_name": _("Warehouse wise Stock Value"),
- "chart_type": "Custom",
- "doctype": "Dashboard Chart",
- "filters_json": json.dumps({}),
- "is_custom": 0,
- "is_public": 1,
- "owner": "Administrator",
- "source": "Warehouse wise Stock Value",
- "type": "Bar"
- },
- {
- "name": "Oldest Items",
- "chart_name": _("Oldest Items"),
- "chart_type": "Report",
- "custom_options": json.dumps({
- "colors": ["#5e64ff"]
- }),
- "doctype": "Dashboard Chart",
- "filters_json": json.dumps({
- "company": company.name,
- "to_date": nowdate(),
- "show_warehouse_wise_stock": 0
- }),
- "is_custom": 1,
- "is_public": 1,
- "owner": "Administrator",
- "report_name": "Stock Ageing",
- "type": "Bar"
- },
- {
- "name": "Item Shortage Summary",
- "chart_name": _("Item Shortage Summary"),
- "chart_type": "Report",
- "doctype": "Dashboard Chart",
- "filters_json": json.dumps({
- "company": company.name
- }),
- "is_custom": 1,
- "is_public": 1,
- "owner": "Administrator",
- "report_name": "Item Shortage Report",
- "type": "Bar"
- }
- ]
-
-def get_number_cards(company, fiscal_year_name, start_date, end_date):
- return [
- {
- "name": "Total Active Items",
- "label": _("Total Active Items"),
- "function": "Count",
- "doctype": "Number Card",
- "document_type": "Item",
- "filters_json": json.dumps([["Item", "disabled", "=", 0]]),
- "is_public": 1,
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Monthly"
- },
- {
- "name": "Total Warehouses",
- "label": _("Total Warehouses"),
- "function": "Count",
- "doctype": "Number Card",
- "document_type": "Warehouse",
- "filters_json": json.dumps([["Warehouse", "disabled", "=", 0]]),
- "is_public": 1,
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Monthly"
- },
- {
- "name": "Total Stock Value",
- "label": _("Total Stock Value"),
- "function": "Sum",
- "aggregate_function_based_on": "stock_value",
- "doctype": "Number Card",
- "document_type": "Bin",
- "filters_json": json.dumps([]),
- "is_public": 1,
- "owner": "Administrator",
- "show_percentage_stats": 1,
- "stats_time_interval": "Daily"
- }
- ]
\ No newline at end of file
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index a091ac7..c8424f1 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -143,7 +143,7 @@
@frappe.whitelist()
-def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
+def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=None, posting_time=None):
"""Returns batch actual qty if warehouse is passed,
or returns dict of qty by warehouse if warehouse is None
@@ -155,9 +155,14 @@
out = 0
if batch_no and warehouse:
+ cond = ""
+ if posting_date and posting_time:
+ cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format(posting_date,
+ posting_time)
+
out = float(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry`
- where warehouse=%s and batch_no=%s""",
+ where warehouse=%s and batch_no=%s {0}""".format(cond),
(warehouse, batch_no))[0][0] or 0)
if batch_no and not warehouse:
diff --git a/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json b/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json
index 07992b8..7eeb147 100644
--- a/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json
+++ b/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json
@@ -71,7 +71,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
- "reqd": 1,
+ "reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@@ -88,7 +88,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-09-12 09:30:03.951743",
+ "modified": "2020-06-26 09:30:03.951743",
"modified_by": "Administrator",
"module": "Stock",
"name": "Customs Tariff Number",
@@ -143,4 +143,4 @@
"track_changes": 1,
"track_seen": 0,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 84d2057..66efcf8 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -801,6 +801,7 @@
"fieldname": "base_in_words",
"fieldtype": "Data",
"label": "In Words (Company Currency)",
+ "length": 240,
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -851,6 +852,7 @@
"fieldname": "in_words",
"fieldtype": "Data",
"label": "In Words",
+ "length": 240,
"oldfieldname": "in_words_export",
"oldfieldtype": "Data",
"print_hide": 1,
@@ -1253,7 +1255,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-19 17:03:45.880106",
+ "modified": "2020-07-18 05:13:55.580420",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index a921a56..4b04a0a 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -537,11 +537,8 @@
dt = make_delivery_trip(dn.name)
self.assertEqual(dn.name, dt.delivery_stops[0].delivery_note)
- def test_delivery_note_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ def test_delivery_note_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - TCP1"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory")
@@ -567,13 +564,8 @@
}
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
- def test_delivery_note_for_disable_allow_cost_center_in_entry_of_bs_account(self):
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
+ def test_delivery_note_cost_center_with_balance_sheet_account(self):
cost_center = "Main - TCP1"
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
@@ -583,7 +575,11 @@
make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100)
stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory')
- dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1")
+ dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1",
+ do_not_submit=1)
+
+ dn.get('items')[0].cost_center = None
+ dn.submit()
gl_entries = get_gl_entries("Delivery Note", dn.name)
@@ -593,7 +589,7 @@
"cost_center": cost_center
},
stock_in_hand_account: {
- "cost_center": None
+ "cost_center": cost_center
}
}
for i, gle in enumerate(gl_entries):
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 7ea2de2..3d57f47 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -1,5 +1,4 @@
{
- "actions": [],
"autoname": "hash",
"creation": "2013-04-22 13:15:44",
"doctype": "DocType",
@@ -82,6 +81,7 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
+ "project",
"section_break_72",
"page_break"
],
@@ -702,6 +702,12 @@
"fieldtype": "Column Break"
},
{
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ },
+ {
"fieldname": "dn_detail",
"fieldtype": "Data",
"hidden": 1,
@@ -714,7 +720,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-03-05 14:18:33.131672",
+ "modified": "2020-07-20 12:25:06.177894",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index c371999..963c87a 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -987,6 +987,7 @@
"read_only": 1
},
{
+ "collapsible": 1,
"depends_on": "eval:(!doc.is_item_from_hub)",
"fieldname": "hub_publishing_sb",
"fieldtype": "Section Break",
@@ -1060,7 +1061,7 @@
"image_field": "image",
"links": [],
"max_attachments": 1,
- "modified": "2020-04-08 15:56:06.195722",
+ "modified": "2020-06-30 12:01:07.534447",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
@@ -1122,4 +1123,4 @@
"sort_order": "DESC",
"title_field": "item_name",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index a75ee67..991ec47 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -13,7 +13,7 @@
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
from frappe import _, msgprint
from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
- now_datetime, random_string, strip)
+ now_datetime, random_string, strip, get_link_to_form, nowtime)
from frappe.utils.html_utils import clean_html
from frappe.website.doctype.website_slideshow.website_slideshow import \
get_slideshow
@@ -194,7 +194,7 @@
if default_warehouse:
stock_entry = make_stock_entry(item_code=self.name, target=default_warehouse, qty=self.opening_stock,
- rate=self.valuation_rate, company=default.company)
+ rate=self.valuation_rate, company=default.company, posting_date=getdate(), posting_time=nowtime())
stock_entry.add_comment("Comment", _("Opening Stock"))
@@ -634,6 +634,9 @@
+ ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
def after_rename(self, old_name, new_name, merge):
+ if merge:
+ self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name)
+
if self.route:
invalidate_cache_for_item(self)
clear_cache(self.route)
@@ -656,6 +659,27 @@
frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
json.dumps(item_wise_tax_detail), update_modified=False)
+ def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name):
+ records = frappe.db.sql(""" SELECT parent, COUNT(*) as records
+ FROM `tabStock Reconciliation Item`
+ WHERE item_code = %s and docstatus = 1
+ GROUP By item_code, warehouse, parent
+ HAVING records > 1
+ """, new_name, as_dict=1)
+
+ if not records: return
+ document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations")
+
+ msg = _("The items {0} and {1} are present in the following {2} : <br>"
+ .format(frappe.bold(old_name), frappe.bold(new_name), document))
+
+ msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
+
+ msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}"
+ .format(frappe.bold(old_name)))
+
+ frappe.throw(_(msg), title=_("Merge not allowed"))
+
def set_last_purchase_rate(self, new_name):
last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0)
frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate)
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index 6c1a559..9ca887c 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -92,8 +92,7 @@
{
"doctype": "Item Tax",
"parentfield": "taxes",
- "item_tax_template": "_Test Account Excise Duty @ 10",
- "tax_category": ""
+ "item_tax_template": "_Test Account Excise Duty @ 10"
}
],
"stock_uom": "_Test UOM 1"
@@ -371,8 +370,7 @@
{
"doctype": "Item Tax",
"parentfield": "taxes",
- "item_tax_template": "_Test Account Excise Duty @ 10",
- "tax_category": ""
+ "item_tax_template": "_Test Account Excise Duty @ 10"
},
{
"doctype": "Item Tax",
@@ -451,14 +449,13 @@
{
"doctype": "Item Tax",
"parentfield": "taxes",
- "item_tax_template": "_Test Account Excise Duty @ 20",
- "tax_category": ""
+ "item_tax_template": "_Test Account Excise Duty @ 20"
},
{
"doctype": "Item Tax",
"parentfield": "taxes",
- "item_tax_template": "_Test Item Tax Template 1",
- "tax_category": "_Test Tax Category 1"
+ "tax_category": "_Test Tax Category 1",
+ "item_tax_template": "_Test Item Tax Template 1"
}
]
}
diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py
index da0c3b7..190cb62 100644
--- a/erpnext/stock/doctype/item_alternative/item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/item_alternative.py
@@ -42,6 +42,8 @@
'alternative_item_code': self.alternative_item_code, 'name': ('!=', self.name)}):
frappe.throw(_("Already record exists for the item {0}").format(self.item_code))
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_alternative_items(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(""" (select alternative_item_code from `tabItem Alternative`
where item_code = %(item_code)s and alternative_item_code like %(txt)s)
@@ -52,4 +54,4 @@
""".format(start, page_len), {
"item_code": filters.get('item_code'),
"txt": '%' + txt + '%'
- })
\ No newline at end of file
+ })
diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json
index 2e0ddfd..5f62381 100644
--- a/erpnext/stock/doctype/item_price/item_price.json
+++ b/erpnext/stock/doctype/item_price/item_price.json
@@ -172,7 +172,7 @@
"default": "Today",
"fieldname": "valid_from",
"fieldtype": "Date",
- "label": "Valid From "
+ "label": "Valid From"
},
{
"default": "0",
@@ -187,7 +187,7 @@
{
"fieldname": "valid_upto",
"fieldtype": "Date",
- "label": "Valid Upto "
+ "label": "Valid Upto"
},
{
"fieldname": "section_break_24",
@@ -208,7 +208,7 @@
"icon": "fa fa-flag",
"idx": 1,
"links": [],
- "modified": "2020-02-28 14:21:25.580331",
+ "modified": "2020-07-06 22:31:32.943475",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Price",
diff --git a/erpnext/stock/doctype/item_tax/item_tax.json b/erpnext/stock/doctype/item_tax/item_tax.json
index a93e463..ae36efc 100644
--- a/erpnext/stock/doctype/item_tax/item_tax.json
+++ b/erpnext/stock/doctype/item_tax/item_tax.json
@@ -1,5 +1,4 @@
{
- "actions": [],
"creation": "2013-02-22 01:28:01",
"doctype": "DocType",
"editable_grid": 1,
@@ -38,8 +37,7 @@
],
"idx": 1,
"istable": 1,
- "links": [],
- "modified": "2019-12-28 21:54:40.807849",
+ "modified": "2020-06-25 01:40:28.859752",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Tax",
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 3a8deb6..3c4e353 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -49,17 +49,21 @@
// set schedule_date
set_schedule_date(frm);
- let filters = {'company': frm.doc.company}
-
- frm.set_query("warehouse", "items", function() {
+ frm.set_query("warehouse", "items", function(doc) {
return {
- filters: filters
+ filters: {'company': doc.company}
};
});
- frm.set_query("set_warehouse", function(){
+ frm.set_query("set_warehouse", function(doc){
return {
- filters: filters
+ filters: {'company': doc.company}
+ };
+ });
+
+ frm.set_query("set_from_warehouse", function(doc){
+ return {
+ filters: {'company': doc.company}
};
});
},
@@ -180,9 +184,8 @@
});
},
- get_item_data: function(frm, item) {
+ get_item_data: function(frm, item, overwrite_warehouse=false) {
if (item && !item.item_code) { return; }
-
frm.call({
method: "erpnext.stock.get_item_details.get_item_details",
child: item,
@@ -203,7 +206,8 @@
plc_conversion_rate: 1,
rate: item.rate,
conversion_factor: item.conversion_factor
- }
+ },
+ overwrite_warehouse: overwrite_warehouse
},
callback: function(r) {
const d = item;
@@ -354,29 +358,29 @@
}
const item = locals[doctype][name];
- frm.events.get_item_data(frm, item);
+ frm.events.get_item_data(frm, item, false);
},
from_warehouse: function(frm, doctype, name) {
const item = locals[doctype][name];
- frm.events.get_item_data(frm, item);
+ frm.events.get_item_data(frm, item, false);
},
warehouse: function(frm, doctype, name) {
const item = locals[doctype][name];
- frm.events.get_item_data(frm, item);
+ frm.events.get_item_data(frm, item, false);
},
rate: function(frm, doctype, name) {
const item = locals[doctype][name];
- frm.events.get_item_data(frm, item);
+ frm.events.get_item_data(frm, item, false);
},
item_code: function(frm, doctype, name) {
const item = locals[doctype][name];
item.rate = 0;
set_schedule_date(frm);
- frm.events.get_item_data(frm, item);
+ frm.events.get_item_data(frm, item, true);
},
schedule_date: function(frm, cdt, cdn) {
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 97606f4..335175f 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -370,6 +370,7 @@
return supplier_items
@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, page_len, filters):
conditions = ""
if txt:
@@ -402,6 +403,8 @@
return material_requests
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def get_default_supplier_query(doctype, txt, searchfield, start, page_len, filters):
doc = frappe.get_doc("Material Request", filters.get("doc"))
item_list = []
@@ -567,4 +570,4 @@
doc.set_item_locations()
- return doc
\ No newline at end of file
+ return doc
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py
index 7a5ae31..a7a29cc 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -175,6 +175,8 @@
self.update_item_details()
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def item_details(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
return frappe.db.sql("""select name, item_name, description from `tabItem`
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 3a5ef76..ee218f2 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -3,6 +3,9 @@
frappe.ui.form.on('Pick List', {
setup: (frm) => {
+ frm.set_indicator_formatter('item_code',
+ function(doc) { return (doc.stock_qty === 0) ? "red" : "green"; });
+
frm.custom_make_buttons = {
'Delivery Note': 'Delivery Note',
'Stock Entry': 'Stock Entry',
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 4b8b594..0da57b7 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -26,11 +26,12 @@
continue
if not item.serial_no:
frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}".format(
- frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse))))
+ frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse))),
+ title=_("Serial Nos Required"))
if len(item.serial_no.split('\n')) == item.picked_qty:
continue
frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
- .format(frappe.bold(item.item_code), frappe.bold(item.idx)))
+ .format(frappe.bold(item.item_code), frappe.bold(item.idx)), title=_("Quantity Mismatch"))
def set_item_locations(self, save=False):
items = self.aggregate_item_qty()
@@ -40,6 +41,9 @@
if self.parent_warehouse:
from_warehouses = frappe.db.get_descendants('Warehouse', self.parent_warehouse)
+ # Create replica before resetting, to handle empty table on update after submit.
+ locations_replica = self.get('locations')
+
# reset
self.delete_key('locations')
for item_doc in items:
@@ -48,7 +52,7 @@
self.item_location_map.setdefault(item_code,
get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code), self.company))
- locations = get_items_with_location_and_quantity(item_doc, self.item_location_map)
+ locations = get_items_with_location_and_quantity(item_doc, self.item_location_map, self.docstatus)
item_doc.idx = None
item_doc.name = None
@@ -62,6 +66,16 @@
location.update(row)
self.append('locations', location)
+ # If table is empty on update after submit, set stock_qty, picked_qty to 0 so that indicator is red
+ # and give feedback to the user. This is to avoid empty Pick Lists.
+ if not self.get('locations') and self.docstatus == 1:
+ for location in locations_replica:
+ location.stock_qty = 0
+ location.picked_qty = 0
+ self.append('locations', location)
+ frappe.msgprint(_("Please Restock Items and Update the Pick List to continue. To discontinue, cancel the Pick List."),
+ title=_("Out of Stock"), indicator="red")
+
if save:
self.save()
@@ -97,11 +111,13 @@
if not pick_list.locations:
frappe.throw(_("Add items in the Item Locations table"))
-def get_items_with_location_and_quantity(item_doc, item_location_map):
+def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus):
available_locations = item_location_map.get(item_doc.item_code)
locations = []
- remaining_stock_qty = item_doc.stock_qty
+ # if stock qty is zero on submitted entry, show positive remaining qty to recalculate in case of restock.
+ remaining_stock_qty = item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty
+
while remaining_stock_qty > 0 and available_locations:
item_location = available_locations.pop(0)
item_location = frappe._dict(item_location)
@@ -119,13 +135,11 @@
if item_location.serial_no:
serial_nos = '\n'.join(item_location.serial_no[0: cint(stock_qty)])
- auto_set_serial_no = frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo")
-
locations.append(frappe._dict({
'qty': qty,
'stock_qty': stock_qty,
'warehouse': item_location.warehouse,
- 'serial_no': serial_nos if auto_set_serial_no else item_doc.serial_no,
+ 'serial_no': serial_nos,
'batch_no': item_location.batch_no
}))
@@ -137,7 +151,7 @@
item_location.qty = qty_diff
if item_location.serial_no:
# set remaining serial numbers
- item_location.serial_no = item_location.serial_no[-qty_diff:]
+ item_location.serial_no = item_location.serial_no[-int(qty_diff):]
available_locations = [item_location] + available_locations
# update available locations for the item
@@ -146,9 +160,14 @@
def get_available_item_locations(item_code, from_warehouses, required_qty, company, ignore_validation=False):
locations = []
- if frappe.get_cached_value('Item', item_code, 'has_serial_no'):
+ has_serial_no = frappe.get_cached_value('Item', item_code, 'has_serial_no')
+ has_batch_no = frappe.get_cached_value('Item', item_code, 'has_batch_no')
+
+ if has_batch_no and has_serial_no:
+ locations = get_available_item_locations_for_serial_and_batched_item(item_code, from_warehouses, required_qty, company)
+ elif has_serial_no:
locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty, company)
- elif frappe.get_cached_value('Item', item_code, 'has_batch_no'):
+ elif has_batch_no:
locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company)
else:
locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company)
@@ -158,8 +177,9 @@
remaining_qty = required_qty - total_qty_available
if remaining_qty > 0 and not ignore_validation:
- frappe.msgprint(_('{0} units of {1} is not available.')
- .format(remaining_qty, frappe.get_desk_link('Item', item_code)))
+ frappe.msgprint(_('{0} units of Item {1} is not available.')
+ .format(remaining_qty, frappe.get_desk_link('Item', item_code)),
+ title=_("Insufficient Stock"))
return locations
@@ -226,6 +246,34 @@
return batch_locations
+def get_available_item_locations_for_serial_and_batched_item(item_code, from_warehouses, required_qty, company):
+ # Get batch nos by FIFO
+ locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company)
+
+ filters = frappe._dict({
+ 'item_code': item_code,
+ 'company': company,
+ 'warehouse': ['!=', ''],
+ 'batch_no': ''
+ })
+
+ # Get Serial Nos by FIFO for Batch No
+ for location in locations:
+ filters.batch_no = location.batch_no
+ filters.warehouse = location.warehouse
+ location.qty = required_qty if location.qty > required_qty else location.qty # if extra qty in batch
+
+ serial_nos = frappe.get_list('Serial No',
+ fields=['name'],
+ filters=filters,
+ limit=location.qty,
+ order_by='purchase_date')
+
+ serial_nos = [sn.name for sn in serial_nos]
+ location.serial_no = serial_nos
+
+ return locations
+
def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company):
# gets all items available in different warehouses
warehouses = [x.get('name') for x in frappe.get_list("Warehouse", {'company': company}, "name")]
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 1b9ff41..8ea7f89d 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -7,6 +7,8 @@
import unittest
test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \
import EmptyStockReconciliationItemsError
@@ -49,7 +51,7 @@
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
- def test_pick_list_splits_row_according_to_warhouse_availability(self):
+ def test_pick_list_splits_row_according_to_warehouse_availability(self):
try:
frappe.get_doc({
'doctype': 'Stock Reconciliation',
@@ -122,7 +124,10 @@
}]
})
- stock_reconciliation.submit()
+ try:
+ stock_reconciliation.submit()
+ except EmptyStockReconciliationItemsError:
+ pass
pick_list = frappe.get_doc({
'doctype': 'Pick List',
@@ -145,6 +150,85 @@
self.assertEqual(pick_list.locations[0].qty, 5)
self.assertEqual(pick_list.locations[0].serial_no, '123450\n123451\n123452\n123453\n123454')
+ def test_pick_list_shows_batch_no_for_batched_item(self):
+ # check if oldest batch no is picked
+ item = frappe.db.exists("Item", {'item_name': 'Batched Item'})
+ if not item:
+ item = create_item("Batched Item")
+ item.has_batch_no = 1
+ item.create_new_batch = 1
+ item.batch_number_series = "B-BATCH-.##"
+ item.save()
+ else:
+ item = frappe.get_doc("Item", {'item_name': 'Batched Item'})
+
+ pr1 = make_purchase_receipt(item_code="Batched Item", qty=1, rate=100.0)
+
+ pr1.load_from_db()
+ oldest_batch_no = pr1.items[0].batch_no
+
+ pr2 = make_purchase_receipt(item_code="Batched Item", qty=2, rate=100.0)
+
+ pick_list = frappe.get_doc({
+ 'doctype': 'Pick List',
+ 'company': '_Test Company',
+ 'purpose': 'Material Transfer',
+ 'locations': [{
+ 'item_code': 'Batched Item',
+ 'qty': 1,
+ 'stock_qty': 1,
+ 'conversion_factor': 1,
+ }]
+ })
+ pick_list.set_item_locations()
+
+ self.assertEqual(pick_list.locations[0].batch_no, oldest_batch_no)
+
+ pr1.cancel()
+ pr2.cancel()
+
+
+ def test_pick_list_for_batched_and_serialised_item(self):
+ # check if oldest batch no and serial nos are picked
+ item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'})
+ if not item:
+ item = create_item("Batched and Serialised Item")
+ item.has_batch_no = 1
+ item.create_new_batch = 1
+ item.has_serial_no = 1
+ item.batch_number_series = "B-BATCH-.##"
+ item.serial_no_series = "S-.####"
+ item.save()
+ else:
+ item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'})
+
+ pr1 = make_purchase_receipt(item_code="Batched and Serialised Item", qty=2, rate=100.0)
+
+ pr1.load_from_db()
+ oldest_batch_no = pr1.items[0].batch_no
+ oldest_serial_nos = pr1.items[0].serial_no
+
+ pr2 = make_purchase_receipt(item_code="Batched and Serialised Item", qty=2, rate=100.0)
+
+ pick_list = frappe.get_doc({
+ 'doctype': 'Pick List',
+ 'company': '_Test Company',
+ 'purpose': 'Material Transfer',
+ 'locations': [{
+ 'item_code': 'Batched and Serialised Item',
+ 'qty': 2,
+ 'stock_qty': 2,
+ 'conversion_factor': 1,
+ }]
+ })
+ pick_list.set_item_locations()
+
+ self.assertEqual(pick_list.locations[0].batch_no, oldest_batch_no)
+ self.assertEqual(pick_list.locations[0].serial_no, oldest_serial_nos)
+
+ pr1.cancel()
+ pr2.cancel()
+
def test_pick_list_for_items_from_multiple_sales_orders(self):
try:
frappe.get_doc({
diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
index 71fbf9a..8665986 100644
--- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json
+++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
@@ -180,7 +180,7 @@
],
"istable": 1,
"links": [],
- "modified": "2020-03-13 19:08:21.995986",
+ "modified": "2020-06-24 17:18:57.357120",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List Item",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 44d5f69..92e33ca 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -1,6 +1,7 @@
{
"actions": [],
"allow_import": 1,
+ "allow_workflow": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:39",
"doctype": "DocType",
@@ -104,6 +105,7 @@
"bill_no",
"bill_date",
"more_info",
+ "project",
"status",
"amended_from",
"range",
@@ -132,17 +134,13 @@
{
"fieldname": "supplier_section",
"fieldtype": "Section Break",
- "options": "fa fa-user",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-user"
},
{
"fieldname": "column_break0",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -153,9 +151,7 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "naming_series",
@@ -167,9 +163,7 @@
"options": "MAT-PRE-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"bold": 1,
@@ -184,8 +178,6 @@
"print_width": "150px",
"reqd": 1,
"search_index": 1,
- "show_days": 1,
- "show_seconds": 1,
"width": "150px"
},
{
@@ -196,24 +188,18 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Supplier Name",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "supplier_delivery_note",
"fieldtype": "Data",
- "label": "Supplier Delivery Note",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Supplier Delivery Note"
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -228,8 +214,6 @@
"print_width": "100px",
"reqd": 1,
"search_index": 1,
- "show_days": 1,
- "show_seconds": 1,
"width": "100px"
},
{
@@ -243,8 +227,6 @@
"print_hide": 1,
"print_width": "100px",
"reqd": 1,
- "show_days": 1,
- "show_seconds": 1,
"width": "100px"
},
{
@@ -253,9 +235,7 @@
"fieldname": "set_posting_time",
"fieldtype": "Check",
"label": "Edit Posting Date and Time",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "company",
@@ -269,8 +249,6 @@
"print_width": "150px",
"remember_last_selected_value": 1,
"reqd": 1,
- "show_days": 1,
- "show_seconds": 1,
"width": "150px"
},
{
@@ -280,9 +258,7 @@
"label": "Is Return",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"depends_on": "is_return",
@@ -292,60 +268,46 @@
"no_copy": 1,
"options": "Purchase Receipt",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Address and Contact"
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"in_global_search": 1,
"label": "Contact",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "contact_email",
@@ -353,42 +315,32 @@
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-tag"
},
{
"fieldname": "currency",
@@ -398,9 +350,7 @@
"oldfieldtype": "Select",
"options": "Currency",
"print_hide": 1,
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"description": "Rate at which supplier's currency is converted to company's base currency",
@@ -411,17 +361,13 @@
"oldfieldtype": "Currency",
"precision": "9",
"print_hide": 1,
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -429,9 +375,7 @@
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"depends_on": "buying_price_list",
@@ -440,9 +384,7 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"depends_on": "buying_price_list",
@@ -450,9 +392,7 @@
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"default": "0",
@@ -461,15 +401,11 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"description": "Sets 'Accepted Warehouse' in each row of the items table.",
@@ -477,9 +413,7 @@
"fieldtype": "Link",
"label": "Accepted Warehouse",
"options": "Warehouse",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"description": "Sets 'Rejected Warehouse' in each row of the items table.",
@@ -490,15 +424,11 @@
"oldfieldname": "rejected_warehouse",
"oldfieldtype": "Link",
"options": "Warehouse",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"default": "No",
@@ -508,9 +438,7 @@
"oldfieldname": "is_subcontracted",
"oldfieldtype": "Select",
"options": "No\nYes",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
@@ -523,17 +451,13 @@
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
- "show_days": 1,
- "show_seconds": 1,
"width": "50px"
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-shopping-cart"
},
{
"allow_bulk_edit": 1,
@@ -543,26 +467,20 @@
"oldfieldname": "purchase_receipt_details",
"oldfieldtype": "Table",
"options": "Purchase Receipt Item",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"collapsible": 1,
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
- "label": "Pricing Rules",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Pricing Rules"
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"depends_on": "supplied_items",
@@ -571,9 +489,7 @@
"label": "Get Current Stock",
"oldfieldtype": "Button",
"options": "get_current_stock",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"collapsible": 1,
@@ -584,9 +500,7 @@
"oldfieldtype": "Section Break",
"options": "fa fa-table",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "supplied_items",
@@ -597,24 +511,18 @@
"oldfieldtype": "Table",
"options": "Purchase Receipt Item Supplied",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "section_break0",
"fieldtype": "Section Break",
- "oldfieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Section Break"
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_total",
@@ -622,9 +530,7 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_net_total",
@@ -637,24 +543,18 @@
"print_width": "150px",
"read_only": 1,
"reqd": 1,
- "show_days": 1,
- "show_seconds": 1,
"width": "150px"
},
{
"fieldname": "column_break_27",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "net_total",
@@ -664,56 +564,42 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"description": "Add / Edit Taxes and Charges",
"fieldname": "taxes_charges_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-money"
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "shipping_col",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
- "options": "Shipping Rule",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Shipping Rule"
},
{
"fieldname": "taxes_section",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "taxes_and_charges",
@@ -722,9 +608,7 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "taxes",
@@ -732,17 +616,13 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Purchase Taxes and Charges"
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Tax Breakup"
},
{
"fieldname": "other_charges_calculation",
@@ -751,17 +631,13 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-money"
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -771,9 +647,7 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -783,9 +657,7 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -795,16 +667,12 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break3",
"fieldtype": "Column Break",
"print_width": "50%",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -815,9 +683,7 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -827,9 +693,7 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -837,18 +701,14 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "section_break_42",
"fieldtype": "Section Break",
- "label": "Additional Discount",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Additional Discount"
},
{
"default": "Grand Total",
@@ -856,9 +716,7 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "base_discount_amount",
@@ -866,38 +724,28 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_44",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "section_break_46",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"fieldname": "base_grand_total",
@@ -907,9 +755,7 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_rounding_adjustment",
@@ -918,20 +764,17 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_in_words",
"fieldtype": "Data",
"label": "In Words (Company Currency)",
+ "length": 240,
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "base_rounded_total",
@@ -941,15 +784,11 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "column_break_50",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "grand_total",
@@ -959,9 +798,7 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "rounding_adjustment",
@@ -970,9 +807,7 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -982,28 +817,23 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "in_words",
"fieldtype": "Data",
"label": "In Words",
+ "length": 240,
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"default": "0",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Disable Rounded Total"
},
{
"collapsible": 1,
@@ -1012,9 +842,7 @@
"fieldtype": "Section Break",
"label": "Terms and Conditions",
"oldfieldtype": "Section Break",
- "options": "fa fa-legal",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-legal"
},
{
"fieldname": "tc_name",
@@ -1023,18 +851,14 @@
"oldfieldname": "tc_name",
"oldfieldtype": "Link",
"options": "Terms and Conditions",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
"label": "Terms and Conditions",
"oldfieldname": "terms",
- "oldfieldtype": "Text Editor",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Text Editor"
},
{
"fieldname": "bill_no",
@@ -1043,9 +867,7 @@
"label": "Bill No",
"oldfieldname": "bill_no",
"oldfieldtype": "Data",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "bill_date",
@@ -1054,9 +876,7 @@
"label": "Bill Date",
"oldfieldname": "bill_date",
"oldfieldtype": "Date",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"collapsible": 1,
@@ -1064,9 +884,7 @@
"fieldtype": "Section Break",
"label": "More Information",
"oldfieldtype": "Section Break",
- "options": "fa fa-file-text",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-file-text"
},
{
"default": "Draft",
@@ -1083,8 +901,6 @@
"read_only": 1,
"reqd": 1,
"search_index": 1,
- "show_days": 1,
- "show_seconds": 1,
"width": "150px"
},
{
@@ -1100,8 +916,6 @@
"print_hide": 1,
"print_width": "150px",
"read_only": 1,
- "show_days": 1,
- "show_seconds": 1,
"width": "150px"
},
{
@@ -1111,9 +925,7 @@
"label": "Range",
"oldfieldname": "range",
"oldfieldtype": "Data",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break4",
@@ -1121,26 +933,27 @@
"oldfieldtype": "Column Break",
"print_hide": 1,
"print_width": "50%",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
+ "description": "Track this Purchase Receipt against any Project",
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ },
+ {
"fieldname": "per_billed",
"fieldtype": "Percent",
"label": "% Amount Billed",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "subscription_detail",
"fieldtype": "Section Break",
- "label": "Auto Repeat Detail",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Auto Repeat Detail"
},
{
"fieldname": "auto_repeat",
@@ -1149,17 +962,13 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"collapsible": 1,
"fieldname": "printing_settings",
"fieldtype": "Section Break",
- "label": "Printing Settings",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Printing Settings"
},
{
"allow_on_submit": 1,
@@ -1167,9 +976,7 @@
"fieldtype": "Link",
"label": "Letter Head",
"options": "Letter Head",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"allow_on_submit": 1,
@@ -1181,17 +988,13 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "report_hide": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"allow_on_submit": 1,
@@ -1199,15 +1002,11 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"fieldname": "column_break_97",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "other_details",
@@ -1218,8 +1017,6 @@
"options": "<div class=\"columnHeading\">Other Details</div>",
"print_hide": 1,
"print_width": "30%",
- "show_days": 1,
- "show_seconds": 1,
"width": "30%"
},
{
@@ -1227,17 +1024,13 @@
"fieldtype": "Small Text",
"label": "Instructions",
"oldfieldname": "instructions",
- "oldfieldtype": "Text",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Text"
},
{
"fieldname": "remarks",
"fieldtype": "Small Text",
"label": "Remarks",
- "print_hide": 1,
- "show_days": 1,
- "show_seconds": 1
+ "print_hide": 1
},
{
"collapsible": 1,
@@ -1245,25 +1038,19 @@
"fieldname": "transporter_info",
"fieldtype": "Section Break",
"label": "Transporter Details",
- "options": "fa fa-truck",
- "show_days": 1,
- "show_seconds": 1
+ "options": "fa fa-truck"
},
{
"fieldname": "transporter_name",
"fieldtype": "Data",
"label": "Transporter Name",
"oldfieldname": "transporter_name",
- "oldfieldtype": "Data",
- "show_days": 1,
- "show_seconds": 1
+ "oldfieldtype": "Data"
},
{
"fieldname": "column_break5",
"fieldtype": "Column Break",
"print_width": "50%",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -1274,8 +1061,6 @@
"oldfieldname": "lr_no",
"oldfieldtype": "Data",
"print_width": "100px",
- "show_days": 1,
- "show_seconds": 1,
"width": "100px"
},
{
@@ -1286,8 +1071,6 @@
"oldfieldname": "lr_date",
"oldfieldtype": "Date",
"print_width": "100px",
- "show_days": 1,
- "show_seconds": 1,
"width": "100px"
},
{
@@ -1296,48 +1079,38 @@
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "inter_company_reference",
"fieldtype": "Link",
"label": "Inter Company Reference",
"options": "Delivery Note",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Scan Barcode"
},
{
"fieldname": "billing_address",
"fieldtype": "Link",
"label": "Select Billing Address",
- "options": "Address",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Address"
},
{
"fieldname": "billing_address_display",
"fieldtype": "Small Text",
"label": "Billing Address",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
}
],
"icon": "fa fa-truck",
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2020-06-13 22:26:03.600092",
+ "modified": "2020-07-18 05:19:12.148115",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 649cfdc..d97b9e8 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -403,11 +403,8 @@
pr_return.submit()
- def test_purchase_receipt_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ def test_purchase_receipt_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
- accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - TCP1"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory")
@@ -435,14 +432,7 @@
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
-
- def test_purchase_receipt_for_disable_allow_cost_center_in_entry_of_bs_account(self):
- accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
- accounts_settings.save()
-
+ def test_purchase_receipt_cost_center_with_balance_sheet_account(self):
if not frappe.db.exists('Location', 'Test Location'):
frappe.get_doc({
'doctype': 'Location',
@@ -454,13 +444,14 @@
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertTrue(gl_entries)
+ cost_center = pr.get('items')[0].cost_center
expected_values = {
"Stock Received But Not Billed - TCP1": {
- "cost_center": None
+ "cost_center": cost_center
},
stock_in_hand_account: {
- "cost_center": None
+ "cost_center": cost_center
}
}
for i, gle in enumerate(gl_entries):
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 37ab807..c3bb514 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -58,6 +58,8 @@
.format(parent_doc=self.reference_type, child_doc=doctype),
(quality_inspection, self.modified, self.reference_name, self.item_code))
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def item_query(doctype, txt, searchfield, start, page_len, filters):
if filters.get("from"):
from frappe.desk.reportview import get_match_cond
@@ -86,6 +88,8 @@
page_len = page_len, qi_condition = qi_condition),
{'parent': filters.get('parent'), 'txt': "%%%s%%" % txt})
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
def quality_inspection_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.get_all('Quality Inspection',
limit_start=start,
@@ -118,4 +122,4 @@
}
}, target_doc, postprocess)
- return doc
\ No newline at end of file
+ return doc
diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json
index d9f8b62..3acf3a9 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.json
+++ b/erpnext/stock/doctype/serial_no/serial_no.json
@@ -427,7 +427,7 @@
"icon": "fa fa-barcode",
"idx": 1,
"links": [],
- "modified": "2020-05-21 19:29:58.517772",
+ "modified": "2020-07-20 20:50:16.660433",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial No",
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index f3514c7..f7ff916 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+import json
from frappe.model.naming import make_autoname
from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate
@@ -197,7 +198,7 @@
def after_rename(self, old, new, merge=False):
"""rename serial_no text fields"""
for dt in frappe.db.sql("""select parent from tabDocField
- where fieldname='serial_no' and fieldtype in ('Text', 'Small Text')"""):
+ where fieldname='serial_no' and fieldtype in ('Text', 'Small Text', 'Long Text')"""):
for item in frappe.db.sql("""select name, serial_no from `tab%s`
where serial_no like %s""" % (dt[0], frappe.db.escape('%' + old + '%'))):
@@ -537,15 +538,54 @@
return serial_nos
@frappe.whitelist()
-def auto_fetch_serial_number(qty, item_code, warehouse, batch_nos=None):
- import json
+def auto_fetch_serial_number(qty, item_code, warehouse, batch_nos=None, for_doctype=None):
filters = {
"item_code": item_code,
"warehouse": warehouse,
"delivery_document_no": "",
"sales_invoice": ""
}
- if batch_nos: filters["batch_no"] = ["in", json.loads(batch_nos)]
+
+ if batch_nos:
+ try:
+ filters["batch_no"] = ["in", json.loads(batch_nos)]
+ except:
+ filters["batch_no"] = ["in", [batch_nos]]
+
+ if for_doctype == 'POS Invoice':
+ reserved_serial_nos, unreserved_serial_nos = get_pos_reserved_serial_nos(filters, qty)
+ return unreserved_serial_nos
serial_numbers = frappe.get_list("Serial No", filters=filters, limit=qty, order_by="creation")
return [item['name'] for item in serial_numbers]
+
+@frappe.whitelist()
+def get_pos_reserved_serial_nos(filters, qty=None):
+ batch_no_cond = ""
+ if filters.get("batch_no"):
+ batch_no_cond = "and item.batch_no = {}".format(frappe.db.escape(filters.get('batch_no')))
+
+ reserved_serial_nos_str = [d.serial_no for d in frappe.db.sql("""select item.serial_no as serial_no
+ from `tabPOS Invoice` p, `tabPOS Invoice Item` item
+ where p.name = item.parent
+ and p.consolidated_invoice is NULL
+ and p.docstatus = 1
+ and item.docstatus = 1
+ and item.item_code = %s
+ and item.warehouse = %s
+ {}
+ """.format(batch_no_cond), [filters.get('item_code'), filters.get('warehouse')], as_dict=1)]
+
+ reserved_serial_nos = []
+ for s in reserved_serial_nos_str:
+ if not s: continue
+
+ serial_nos = s.split("\n")
+ serial_nos = ' '.join(serial_nos).split() # remove whitespaces
+ if len(serial_nos): reserved_serial_nos += serial_nos
+
+ filters["name"] = ["not in", reserved_serial_nos]
+ serial_numbers = frappe.get_list("Serial No", filters=filters, limit=qty, order_by="creation")
+ unreserved_serial_nos = [item['name'] for item in serial_numbers]
+
+ return reserved_serial_nos, unreserved_serial_nos
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 0fbc631..8e25804 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -413,7 +413,7 @@
def test_serial_item_error(self):
se, serial_nos = self.test_serial_by_series()
if not frappe.db.exists('Serial No', 'ABCD'):
- make_serialized_item("_Test Serialized Item", "ABCD\nEFGH")
+ make_serialized_item(item_code="_Test Serialized Item", serial_no="ABCD\nEFGH")
se = frappe.copy_doc(test_records[0])
se.purpose = "Material Transfer"
@@ -823,15 +823,29 @@
])
)
-def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
+def make_serialized_item(**args):
+ args = frappe._dict(args)
se = frappe.copy_doc(test_records[0])
- se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series"
- se.get("items")[0].serial_no = serial_no
+
+ if args.company:
+ se.company = args.company
+
+ se.get("items")[0].item_code = args.item_code or "_Test Serialized Item With Series"
+
+ if args.serial_no:
+ se.get("items")[0].serial_no = args.serial_no
+
+ if args.cost_center:
+ se.get("items")[0].cost_center = args.cost_center
+
+ if args.expense_account:
+ se.get("items")[0].expense_account = args.expense_account
+
se.get("items")[0].qty = 2
se.get("items")[0].transfer_qty = 2
- if target_warehouse:
- se.get("items")[0].t_warehouse = target_warehouse
+ if args.target_warehouse:
+ se.get("items")[0].t_warehouse = args.target_warehouse
se.set_stock_entry_type()
se.insert()
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index dd284e4..ecee97c 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -74,6 +74,20 @@
, __("Get Items"), __("Update"));
},
+ posting_date: function(frm) {
+ frm.trigger("set_valuation_rate_and_qty_for_all_items");
+ },
+
+ posting_time: function(frm) {
+ frm.trigger("set_valuation_rate_and_qty_for_all_items");
+ },
+
+ set_valuation_rate_and_qty_for_all_items: function(frm) {
+ frm.doc.items.forEach(row => {
+ frm.events.set_valuation_rate_and_qty(frm, row.doctype, row.name);
+ });
+ },
+
set_valuation_rate_and_qty: function(frm, cdt, cdn) {
var d = frappe.model.get_doc(cdt, cdn);
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 5e469c2..43fbc00 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -184,8 +184,12 @@
sl_entries = []
has_serial_no = False
+ has_batch_no = False
for row in self.items:
item = frappe.get_doc("Item", row.item_code)
+ if item.has_batch_no:
+ has_batch_no = True
+
if item.has_serial_no or item.has_batch_no:
has_serial_no = True
self.get_sle_for_serialized_items(row, sl_entries)
@@ -222,7 +226,11 @@
if has_serial_no:
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
- self.make_sl_entries(sl_entries)
+ allow_negative_stock = False
+ if has_batch_no:
+ allow_negative_stock = True
+
+ self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
if has_serial_no and sl_entries:
self.update_valuation_rate_for_serial_no()
@@ -493,7 +501,7 @@
qty, rate = data
if item_dict.get("has_batch_no"):
- qty = get_batch_qty(batch_no, warehouse) or 0
+ qty = get_batch_qty(batch_no, warehouse, posting_date=posting_date, posting_time=posting_time) or 0
return {
'qty': qty,
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index 5d534af..c0c9fbc 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -10,12 +10,14 @@
"field_order": [
"warehouse_detail",
"warehouse_name",
+ "column_break_3",
+ "warehouse_type",
+ "parent_warehouse",
"is_group",
- "company",
- "disabled",
"column_break_4",
"account",
- "warehouse_type",
+ "company",
+ "disabled",
"address_and_contact",
"address_html",
"column_break_10",
@@ -31,7 +33,6 @@
"state",
"pin",
"tree_details",
- "parent_warehouse",
"lft",
"rgt",
"old_parent"
@@ -44,7 +45,6 @@
"oldfieldtype": "Section Break"
},
{
- "description": "If blank, parent Warehouse Account or company default will be considered",
"fieldname": "warehouse_name",
"fieldtype": "Data",
"label": "Warehouse Name",
@@ -91,6 +91,7 @@
"options": "Account"
},
{
+ "depends_on": "eval:!doc.__islocal",
"fieldname": "address_and_contact",
"fieldtype": "Section Break",
"label": "Address and Contact"
@@ -224,13 +225,17 @@
"fieldtype": "Link",
"label": "Warehouse Type",
"options": "Warehouse Type"
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Section Break"
}
],
"icon": "fa fa-building",
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2020-03-18 18:11:53.282358",
+ "modified": "2020-08-03 11:19:35.943330",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 0ed3b27..1a7c15e 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -47,6 +47,8 @@
"""
args = process_args(args)
+ for_validate = process_string_args(for_validate)
+ overwrite_warehouse = process_string_args(overwrite_warehouse)
item = frappe.get_cached_doc("Item", args.item_code)
validate_item_details(args, item)
@@ -166,6 +168,10 @@
set_transaction_type(args)
return args
+def process_string_args(args):
+ if isinstance(args, string_types):
+ args = json.loads(args)
+ return args
@frappe.whitelist()
def get_item_code(barcode=None, serial_no=None):
@@ -395,13 +401,30 @@
return warehouse
def update_barcode_value(out):
- from erpnext.accounts.doctype.sales_invoice.pos import get_barcode_data
barcode_data = get_barcode_data([out])
# If item has one barcode then update the value of the barcode field
if barcode_data and len(barcode_data.get(out.item_code)) == 1:
out['barcode'] = barcode_data.get(out.item_code)[0]
+def get_barcode_data(items_list):
+ # get itemwise batch no data
+ # exmaple: {'LED-GRE': [Batch001, Batch002]}
+ # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse
+
+ itemwise_barcode = {}
+ for item in items_list:
+ barcodes = frappe.db.sql("""
+ select barcode from `tabItem Barcode` where parent = %s
+ """, item.item_code, as_dict=1)
+
+ for barcode in barcodes:
+ if item.item_code not in itemwise_barcode:
+ itemwise_barcode.setdefault(item.item_code, [])
+ itemwise_barcode[item.item_code].append(barcode.get("barcode"))
+
+ return itemwise_barcode
+
@frappe.whitelist()
def get_item_tax_info(company, tax_category, item_codes):
out = {}
@@ -413,7 +436,7 @@
continue
out[item_code] = {}
item = frappe.get_cached_doc("Item", item_code)
- get_item_tax_template({"tax_category": tax_category}, item, out[item_code])
+ get_item_tax_template({"company": company, "tax_category": tax_category}, item, out[item_code])
out[item_code]["item_tax_rate"] = get_item_tax_map(company, out[item_code].get("item_tax_template"), as_json=True)
return out
@@ -442,7 +465,8 @@
taxes_with_no_validity = []
for tax in taxes:
- if tax.valid_from:
+ tax_company = frappe.get_value("Item Tax Template", tax.item_tax_template, 'company')
+ if tax.valid_from and tax_company == args['company']:
# In purchase Invoice first preference will be given to supplier invoice date
# if supplier date is not present then posting date
validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date')
@@ -450,7 +474,8 @@
if getdate(tax.valid_from) <= getdate(validation_date):
taxes_with_validity.append(tax)
else:
- taxes_with_no_validity.append(tax)
+ if tax_company == args['company']:
+ taxes_with_no_validity.append(tax)
if taxes_with_validity:
taxes = sorted(taxes_with_validity, key = lambda i: i.valid_from, reverse=True)
@@ -637,6 +662,10 @@
conditions += """ and %(transaction_date)s between
ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
+ if args.get('posting_date'):
+ conditions += """ and %(posting_date)s between
+ ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
+
return frappe.db.sql(""" select name, price_list_rate, uom
from `tabItem Price` {conditions}
order by valid_from desc, uom desc """.format(conditions=conditions), args)
@@ -657,6 +686,7 @@
"supplier": args.get('supplier'),
"uom": args.get('uom'),
"transaction_date": args.get('transaction_date'),
+ "posting_date": args.get('posting_date'),
}
item_price_data = 0
diff --git a/erpnext/stock/module_onboarding/stock/stock.json b/erpnext/stock/module_onboarding/stock/stock.json
index 10f05d4..1d5bf8c 100644
--- a/erpnext/stock/module_onboarding/stock/stock.json
+++ b/erpnext/stock/module_onboarding/stock/stock.json
@@ -19,7 +19,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/stock",
"idx": 0,
"is_complete": 0,
- "modified": "2020-06-29 16:41:09.440378",
+ "modified": "2020-07-08 14:22:07.951891",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",
@@ -47,7 +47,7 @@
"step": "Stock Settings"
}
],
- "subtitle": "Inventory, Warehouses, Analysis and more.",
+ "subtitle": "Inventory, Warehouses, Analysis, and more.",
"success_message": "The Stock Module is all set up!",
"title": "Let's Set Up the Stock Module."
}
\ No newline at end of file
diff --git a/erpnext/stock/number_card/total_active_items/total_active_items.json b/erpnext/stock/number_card/total_active_items/total_active_items.json
new file mode 100644
index 0000000..f6863b9
--- /dev/null
+++ b/erpnext/stock/number_card/total_active_items/total_active_items.json
@@ -0,0 +1,20 @@
+{
+ "creation": "2020-07-20 21:01:04.422436",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Item",
+ "filters_json": "[[\"Item\",\"disabled\",\"=\",0]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Active Items",
+ "modified": "2020-07-22 13:08:30.430677",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Total Active Items",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/stock/number_card/total_stock_value/total_stock_value.json b/erpnext/stock/number_card/total_stock_value/total_stock_value.json
new file mode 100644
index 0000000..8e480a6
--- /dev/null
+++ b/erpnext/stock/number_card/total_stock_value/total_stock_value.json
@@ -0,0 +1,21 @@
+{
+ "aggregate_function_based_on": "stock_value",
+ "creation": "2020-07-20 21:01:04.495481",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Bin",
+ "filters_json": "[]",
+ "function": "Sum",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Stock Value",
+ "modified": "2020-07-22 13:08:48.412001",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Total Stock Value",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/stock/number_card/total_warehouses/total_warehouses.json b/erpnext/stock/number_card/total_warehouses/total_warehouses.json
new file mode 100644
index 0000000..ab0836a
--- /dev/null
+++ b/erpnext/stock/number_card/total_warehouses/total_warehouses.json
@@ -0,0 +1,20 @@
+{
+ "creation": "2020-07-20 21:01:04.457598",
+ "docstatus": 0,
+ "doctype": "Number Card",
+ "document_type": "Warehouse",
+ "filters_json": "[[\"Warehouse\",\"disabled\",\"=\",0]]",
+ "function": "Count",
+ "idx": 0,
+ "is_public": 1,
+ "is_standard": 1,
+ "label": "Total Warehouses",
+ "modified": "2020-07-22 13:08:40.258927",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Total Warehouses",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "type": "Document Type"
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
index 557c905..9457dee 100644
--- a/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
+++ b/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
@@ -8,13 +8,13 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-19 18:54:19.383397",
+ "modified": "2020-07-04 12:33:16.970031",
"modified_by": "Administrator",
"name": "Setup your Warehouse",
"owner": "Administrator",
"path": "Tree/Warehouse",
"reference_document": "Warehouse",
"show_full_form": 0,
- "title": "Setup your Warehouse",
+ "title": "Set up your Warehouse",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.js b/erpnext/stock/report/stock_ageing/stock_ageing.js
index ccde61a..8495142 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.js
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.js
@@ -37,6 +37,27 @@
"options": "Brand"
},
{
+ "fieldname":"range1",
+ "label": __("Ageing Range 1"),
+ "fieldtype": "Int",
+ "default": "30",
+ "reqd": 1
+ },
+ {
+ "fieldname":"range2",
+ "label": __("Ageing Range 2"),
+ "fieldtype": "Int",
+ "default": "60",
+ "reqd": 1
+ },
+ {
+ "fieldname":"range3",
+ "label": __("Ageing Range 3"),
+ "fieldtype": "Int",
+ "default": "90",
+ "reqd": 1
+ },
+ {
"fieldname":"show_warehouse_wise_stock",
"label": __("Show Warehouse-wise Stock"),
"fieldtype": "Check",
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index 723ed5c..4af3c54 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -4,12 +4,11 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import date_diff, flt
+from frappe.utils import date_diff, flt, cint
from six import iteritems
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
def execute(filters=None):
-
columns = get_columns(filters)
item_details = get_fifo_queue(filters)
to_date = filters["to_date"]
@@ -25,6 +24,7 @@
average_age = get_average_age(fifo_queue, to_date)
earliest_age = date_diff(to_date, fifo_queue[0][1])
latest_age = date_diff(to_date, fifo_queue[-1][1])
+ range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date)
row = [details.name, details.item_name,
details.description, details.item_group, details.brand]
@@ -33,6 +33,7 @@
row.append(details.warehouse)
row.extend([item_dict.get("total_qty"), average_age,
+ range1, range2, range3, above_range3,
earliest_age, latest_age, details.stock_uom])
data.append(row)
@@ -55,7 +56,25 @@
return flt(age_qty / total_qty, 2) if total_qty else 0.0
+def get_range_age(filters, fifo_queue, to_date):
+ range1 = range2 = range3 = above_range3 = 0.0
+ for item in fifo_queue:
+ age = date_diff(to_date, item[1])
+
+ if age <= filters.range1:
+ range1 += flt(item[0])
+ elif age <= filters.range2:
+ range2 += flt(item[0])
+ elif age <= filters.range3:
+ range3 += flt(item[0])
+ else:
+ above_range3 += flt(item[0])
+
+ return range1, range2, range3, above_range3
+
def get_columns(filters):
+ range_columns = []
+ setup_ageing_columns(filters, range_columns)
columns = [
{
"label": _("Item Code"),
@@ -112,7 +131,9 @@
"fieldname": "average_age",
"fieldtype": "Float",
"width": 100
- },
+ }])
+ columns.extend(range_columns)
+ columns.extend([
{
"label": _("Earliest"),
"fieldname": "earliest",
@@ -187,7 +208,7 @@
transferred_item_details[(d.voucher_no, d.name)].append(fifo_queue.pop(0))
else:
# all from current batch
- batch[0] -= qty_to_pop
+ batch[0] = flt(batch[0]) - qty_to_pop
transferred_item_details[(d.voucher_no, d.name)].append([qty_to_pop, batch[1]])
qty_to_pop = 0
@@ -263,3 +284,18 @@
},
"type" : "bar"
}
+
+def setup_ageing_columns(filters, range_columns):
+ for i, label in enumerate(["0-{range1}".format(range1=filters["range1"]),
+ "{range1}-{range2}".format(range1=cint(filters["range1"])+ 1, range2=filters["range2"]),
+ "{range2}-{range3}".format(range2=cint(filters["range2"])+ 1, range3=filters["range3"]),
+ "{range3}-{above}".format(range3=cint(filters["range3"])+ 1, above=_("Above"))]):
+ add_column(range_columns, label="Age ("+ label +")", fieldname='range' + str(i+1))
+
+def add_column(range_columns, label, fieldname, fieldtype='Float', width=140):
+ range_columns.append(dict(
+ label=label,
+ fieldname=fieldname,
+ fieldtype=fieldtype,
+ width=width
+ ))
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 74a4f6e..042087a 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
from frappe import _
from frappe.utils import flt, cint, getdate, now, date_diff
from erpnext.stock.utils import add_additional_uom_columns
@@ -20,6 +20,11 @@
from_date = filters.get('from_date')
to_date = filters.get('to_date')
+ if filters.get("company"):
+ company_currency = erpnext.get_company_currency(filters.get("company"))
+ else:
+ company_currency = frappe.db.get_single_value("Global Defaults", "default_currency")
+
include_uom = filters.get("include_uom")
columns = get_columns(filters)
items = get_items(filters)
@@ -52,6 +57,7 @@
item_reorder_qty = item_reorder_detail_map[item + warehouse]["warehouse_reorder_qty"]
report_data = {
+ 'currency': company_currency,
'item_code': item,
'warehouse': warehouse,
'company': company,
@@ -89,7 +95,6 @@
def get_columns(filters):
"""return columns"""
-
columns = [
{"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100},
{"label": _("Item Name"), "fieldname": "item_name", "width": 150},
@@ -97,14 +102,14 @@
{"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100},
{"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90},
{"label": _("Balance Qty"), "fieldname": "bal_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100},
+ {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100, "options": "currency"},
{"label": _("Opening Qty"), "fieldname": "opening_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Float", "width": 110},
+ {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Currency", "width": 110, "options": "currency"},
{"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
{"label": _("In Value"), "fieldname": "in_val", "fieldtype": "Float", "width": 80},
{"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
{"label": _("Out Value"), "fieldname": "out_val", "fieldtype": "Float", "width": 80},
- {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate"},
+ {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate", "options": "currency"},
{"label": _("Reorder Level"), "fieldname": "reorder_level", "fieldtype": "Float", "width": 80, "convertible": "qty"},
{"label": _("Reorder Qty"), "fieldname": "reorder_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
{"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 100}
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index abf959e..fe8ad71 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -4,10 +4,10 @@
from __future__ import unicode_literals
import frappe
+from frappe.utils import cint, flt
from erpnext.stock.utils import update_included_uom_in_report
from frappe import _
-
def execute(filters=None):
include_uom = filters.get("include_uom")
columns = get_columns()
@@ -15,6 +15,7 @@
sl_entries = get_stock_ledger_entries(filters, items)
item_details = get_item_details(items, sl_entries, include_uom)
opening_row = get_opening_balance(filters, columns)
+ precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
data = []
conversion_factors = []
@@ -29,10 +30,10 @@
sle.update(item_detail)
if filters.get("batch_no"):
- actual_qty += sle.actual_qty
+ actual_qty += flt(sle.actual_qty, precision)
stock_value += sle.stock_value_difference
- if sle.voucher_type == 'Stock Reconciliation':
+ if sle.voucher_type == 'Stock Reconciliation' and not sle.actual_qty:
actual_qty = sle.qty_after_transaction
stock_value = sle.stock_value
diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
index 6a86889..5873a7a 100644
--- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
+++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
@@ -21,7 +21,7 @@
for cd in consumed_details.get(item_code):
if (cd.voucher_no not in material_transfer_vouchers):
- if cd.voucher_type=="Delivery Note":
+ if cd.voucher_type in ["Delivery Note", "Sales Invoice"]:
delivered_qty += abs(flt(cd.actual_qty))
delivered_amount += abs(flt(cd.stock_value_difference))
elif cd.voucher_type!="Delivery Note":
diff --git a/erpnext/stock/stock_dashboard/stock/stock.json b/erpnext/stock/stock_dashboard/stock/stock.json
new file mode 100644
index 0000000..dee7fed
--- /dev/null
+++ b/erpnext/stock/stock_dashboard/stock/stock.json
@@ -0,0 +1,47 @@
+{
+ "cards": [
+ {
+ "card": "Total Active Items"
+ },
+ {
+ "card": "Total Warehouses"
+ },
+ {
+ "card": "Total Stock Value"
+ }
+ ],
+ "charts": [
+ {
+ "chart": "Warehouse wise Stock Value",
+ "width": "Full"
+ },
+ {
+ "chart": "Purchase Receipt Trends",
+ "width": "Half"
+ },
+ {
+ "chart": "Delivery Trends",
+ "width": "Half"
+ },
+ {
+ "chart": "Oldest Items",
+ "width": "Half"
+ },
+ {
+ "chart": "Item Shortage Summary",
+ "width": "Half"
+ }
+ ],
+ "creation": "2020-07-20 21:01:04.549136",
+ "dashboard_name": "Stock",
+ "docstatus": 0,
+ "doctype": "Dashboard",
+ "idx": 0,
+ "is_default": 1,
+ "is_standard": 1,
+ "modified": "2020-07-22 13:09:33.096694",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock",
+ "owner": "Administrator"
+}
\ No newline at end of file
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index e7e5bd3..9e15757 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -79,7 +79,7 @@
method: "erpnext.support.doctype.issue.issue.make_task",
frm: frm
});
- }, __("Make"));
+ }, __("Create"));
} else {
if (frm.doc.service_level_agreement) {
@@ -232,4 +232,4 @@
} else {
return {"diff_display": "Failed", "indicator": "red"};
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/support/doctype/issue/issue_list.js b/erpnext/support/doctype/issue/issue_list.js
index 6d702f6..513a8dc 100644
--- a/erpnext/support/doctype/issue/issue_list.js
+++ b/erpnext/support/doctype/issue/issue_list.js
@@ -8,11 +8,11 @@
var method = "erpnext.support.doctype.issue.issue.set_multiple_status";
- listview.page.add_menu_item(__("Set as Open"), function() {
+ listview.page.add_action_item(__("Set as Open"), function() {
listview.call_for_selected_items(method, {"status": "Open"});
});
- listview.page.add_menu_item(__("Set as Closed"), function() {
+ listview.page.add_action_item(__("Set as Closed"), function() {
listview.call_for_selected_items(method, {"status": "Closed"});
});
},
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index c692315..70c4696 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -6,7 +6,7 @@
import frappe
from frappe.model.document import Document
from frappe import _
-from frappe.utils import getdate, get_weekdays
+from frappe.utils import getdate, get_weekdays, get_link_to_form
class ServiceLevelAgreement(Document):
@@ -21,8 +21,8 @@
for priority in self.priorities:
# Check if response and resolution time is set for every priority
- if not (priority.response_time or priority.resolution_time):
- frappe.throw(_("Set Response Time and Resolution for Priority {0} at index {1}.").format(priority.priority, priority.idx))
+ if not priority.response_time or not priority.resolution_time:
+ frappe.throw(_("Set Response Time and Resolution Time for Priority {0} in row {1}.").format(priority.priority, priority.idx))
priorities.append(priority.priority)
@@ -33,7 +33,7 @@
resolution = priority.resolution_time
if response > resolution:
- frappe.throw(_("Response Time for {0} at index {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
+ frappe.throw(_("Response Time for {0} priority in row {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
# Check if repeated priority
if not len(set(priorities)) == len(priorities):
@@ -73,8 +73,9 @@
frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
def validate_doc(self):
- if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
- frappe.throw(_("Service Level Agreement tracking is not enabled."))
+ if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement") and self.enable:
+ frappe.throw(_("{0} is not enabled in {1}").format(frappe.bold("Track Service Level Agreement"),
+ get_link_to_form("Support Settings", "Support Settings")))
if self.default_service_level_agreement:
if frappe.db.exists("Service Level Agreement", {"default_service_level_agreement": "1", "name": ["!=", self.name]}):
diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html
index 04f89ec..b8b0d98 100644
--- a/erpnext/templates/generators/item/item_configure.html
+++ b/erpnext/templates/generators/item/item_configure.html
@@ -2,7 +2,7 @@
{% set cart_settings = shopping_cart.cart_settings %}
<div class="mt-3">
- {% if cart_settings.show_configure_button | int %}
+ {% if cart_settings.enable_variants | int %}
<button class="btn btn-primary btn-configure"
data-item-code="{{ doc.name }}"
data-item-name="{{ doc.item_name }}"
diff --git a/erpnext/templates/includes/footer/footer_extension.html b/erpnext/templates/includes/footer/footer_extension.html
index 8cf3081..6171b61 100644
--- a/erpnext/templates/includes/footer/footer_extension.html
+++ b/erpnext/templates/includes/footer/footer_extension.html
@@ -6,7 +6,7 @@
aria-label="{{ _('Your email address...') }}"
aria-describedby="footer-subscribe-button">
<div class="input-group-append">
- <button class="btn btn-outline-secondary"
+ <button class="btn btn-sm btn-outline-secondary"
type="button" id="footer-subscribe-button">{{ _("Get Updates") }}</button>
</div>
</div>
diff --git a/erpnext/accounts/page/pos/__init__.py b/erpnext/utilities/doctype/video/__init__.py
similarity index 100%
copy from erpnext/accounts/page/pos/__init__.py
copy to erpnext/utilities/doctype/video/__init__.py
diff --git a/erpnext/utilities/doctype/video/test_video.py b/erpnext/utilities/doctype/video/test_video.py
new file mode 100644
index 0000000..33ea31c
--- /dev/null
+++ b/erpnext/utilities/doctype/video/test_video.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestVideo(unittest.TestCase):
+ pass
diff --git a/erpnext/utilities/doctype/video/video.js b/erpnext/utilities/doctype/video/video.js
new file mode 100644
index 0000000..056bd3c
--- /dev/null
+++ b/erpnext/utilities/doctype/video/video.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Video', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/utilities/doctype/video/video.json b/erpnext/utilities/doctype/video/video.json
new file mode 100644
index 0000000..5d2cc13
--- /dev/null
+++ b/erpnext/utilities/doctype/video/video.json
@@ -0,0 +1,106 @@
+{
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:title",
+ "creation": "2018-10-17 05:47:13.087395",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "provider",
+ "url",
+ "column_break_4",
+ "publish_date",
+ "duration",
+ "section_break_7",
+ "description"
+ ],
+ "fields": [
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "provider",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Provider",
+ "options": "YouTube\nVimeo",
+ "reqd": 1
+ },
+ {
+ "fieldname": "url",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "URL",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "publish_date",
+ "fieldtype": "Date",
+ "label": "Publish Date"
+ },
+ {
+ "fieldname": "duration",
+ "fieldtype": "Data",
+ "label": "Duration"
+ },
+ {
+ "fieldname": "section_break_7",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Text Editor",
+ "in_list_view": 1,
+ "label": "Description",
+ "reqd": 1
+ }
+ ],
+ "links": [],
+ "modified": "2020-07-21 19:29:46.603734",
+ "modified_by": "Administrator",
+ "module": "Utilities",
+ "name": "Video",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "All",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py b/erpnext/utilities/doctype/video/video.py
similarity index 61%
copy from erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py
copy to erpnext/utilities/doctype/video/video.py
index 87ce842..3c17b56 100644
--- a/erpnext/selling/doctype/pos_closing_voucher_taxes/pos_closing_voucher_taxes.py
+++ b/erpnext/utilities/doctype/video/video.py
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
+# import frappe
from frappe.model.document import Document
-class POSClosingVoucherTaxes(Document):
+class Video(Document):
pass
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 88a6dc0..619d262 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -119,6 +119,15 @@
def validate_rate_with_reference_doc(self, ref_details):
+ buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
+
+ if self.doctype in buying_doctypes:
+ to_disable = "Maintain same rate throughout Purchase cycle"
+ settings_page = "Buying Settings"
+ else:
+ to_disable = "Maintain same rate throughout Sales cycle"
+ settings_page = "Selling Settings"
+
for ref_dt, ref_dn_field, ref_link_field in ref_details:
for d in self.get("items"):
if d.get(ref_link_field):
@@ -128,8 +137,8 @@
frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
.format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.")
- .format(frappe.bold(_("Maintain Same Rate Throughout Sales Cycle")),
- get_link_to_form("Selling Settings", "Selling Settings", frappe.bold("Selling Settings"))))
+ .format(frappe.bold(_(to_disable)),
+ get_link_to_form(settings_page, settings_page, frappe.bold(settings_page))))
def get_link_filters(self, for_doctype):
if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):
diff --git a/requirements.txt b/requirements.txt
index 9da537e..912d61f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,10 +2,12 @@
frappe
gocardless-pro==1.11.0
googlemaps==3.1.1
-pandas==0.24.2
+pandas==1.0.5
plaid-python==3.4.0
+pycountry==19.8.18
PyGithub==1.44.1
python-stdnum==1.12
+taxjar==1.9.0
+tweepy==3.8.0
Unidecode==1.1.1
WooCommerce==2.1.1
-tweepy==3.8.0
\ No newline at end of file