fix: HR dashboard and onboarding
diff --git a/.travis.yml b/.travis.yml
index 213445b..77d427e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,5 @@
-dist: trusty
-
language: python
+dist: trusty
git:
depth: 1
@@ -14,21 +13,10 @@
jobs:
include:
- - name: "Python 2.7 Server Side Test"
- python: 2.7
- script: bench --site test_site run-tests --app erpnext --coverage
-
- name: "Python 3.6 Server Side Test"
python: 3.6
script: bench --site test_site run-tests --app erpnext --coverage
- - name: "Python 2.7 Patch Test"
- python: 2.7
- before_script:
- - wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- - bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
- script: bench --site test_site migrate
-
- name: "Python 3.6 Patch Test"
python: 3.6
before_script:
@@ -40,8 +28,7 @@
- cd ~
- nvm install 10
- - git clone https://github.com/frappe/bench --depth 1
- - pip install -e ./bench
+ - pip install frappe-bench
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench
diff --git a/erpnext/accounts/accounts b/erpnext/accounts/accounts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/accounts
diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
index 5decccb..39bf4b0 100644
--- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
+++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
@@ -14,7 +14,7 @@
@frappe.whitelist()
@cache_source
def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
- to_date = None, timespan = None, time_interval = None):
+ to_date = None, timespan = None, time_interval = None, heatmap_year = None):
if chart_name:
chart = frappe.get_doc('Dashboard Chart', chart_name)
else:
diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py
index a106f70..1eed5a0 100644
--- a/erpnext/accounts/dashboard_fixtures.py
+++ b/erpnext/accounts/dashboard_fixtures.py
@@ -3,114 +3,9 @@
import frappe
import json
-
-
-def get_data():
- return frappe._dict({
- "dashboards": get_dashboards(),
- "charts": get_charts(),
- })
-
-def get_dashboards():
- return [{
- "name": "Accounts",
- "dashboard_name": "Accounts",
- "charts": [
- { "chart": "Outgoing Bills (Sales Invoice)" },
- { "chart": "Incoming Bills (Purchase Invoice)" },
- { "chart": "Bank Balance" },
- { "chart": "Income" },
- { "chart": "Expenses" }
- ]
- }]
-
-def get_charts():
- company = frappe.get_doc("Company", get_company_for_dashboards())
- income_account = company.default_income_account or get_account("Income Account", company.name)
- expense_account = company.default_expense_account or get_account("Expense Account", company.name)
- bank_account = company.default_bank_account or get_account("Bank", company.name)
-
- return [
- {
- "doctype": "Dashboard Chart",
- "time_interval": "Quarterly",
- "name": "Income",
- "chart_name": "Income",
- "timespan": "Last Year",
- "color": None,
- "filters_json": json.dumps({"company": company.name, "account": income_account}),
- "source": "Account Balance Timeline",
- "chart_type": "Custom",
- "timeseries": 1,
- "owner": "Administrator",
- "type": "Line"
- },
- {
- "doctype": "Dashboard Chart",
- "time_interval": "Quarterly",
- "name": "Expenses",
- "chart_name": "Expenses",
- "timespan": "Last Year",
- "color": None,
- "filters_json": json.dumps({"company": company.name, "account": expense_account}),
- "source": "Account Balance Timeline",
- "chart_type": "Custom",
- "timeseries": 1,
- "owner": "Administrator",
- "type": "Line"
- },
- {
- "doctype": "Dashboard Chart",
- "time_interval": "Quarterly",
- "name": "Bank Balance",
- "chart_name": "Bank Balance",
- "timespan": "Last Year",
- "color": "#ffb868",
- "filters_json": json.dumps({"company": company.name, "account": bank_account}),
- "source": "Account Balance Timeline",
- "chart_type": "Custom",
- "timeseries": 1,
- "owner": "Administrator",
- "type": "Line"
- },
- {
- "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_grand_total",
- "filters_json": json.dumps({}),
- "chart_type": "Sum",
- "timeseries": 1,
- "based_on": "posting_date",
- "owner": "Administrator",
- "document_type": "Purchase Invoice",
- "type": "Bar"
- },
- {
- "doctype": "Dashboard Chart",
- "time_interval": "Monthly",
- "name": "Outgoing Bills (Sales Invoice)",
- "chart_name": "Outgoing Bills (Sales Invoice)",
- "timespan": "Last Year",
- "color": "#7b933d",
- "value_based_on": "base_grand_total",
- "filters_json": json.dumps({}),
- "chart_type": "Sum",
- "timeseries": 1,
- "based_on": "posting_date",
- "owner": "Administrator",
- "document_type": "Sales Invoice",
- "type": "Bar"
- }
- ]
-
-def get_account(account_type, company):
- accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company})
- if accounts:
- return accounts[0].name
+from frappe.utils import nowdate, add_months, get_date_str
+from frappe import _
+from erpnext.accounts.utils import get_fiscal_year, get_account_name
def get_company_for_dashboards():
company = frappe.defaults.get_defaults().company
@@ -120,4 +15,250 @@
company_list = frappe.get_list("Company")
if company_list:
return company_list[0].name
- return None
\ No newline at end of file
+ return None
+
+def get_data():
+ return frappe._dict({
+ "dashboards": get_dashboards(),
+ "charts": get_charts(),
+ "number_cards": get_number_cards()
+ })
+
+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():
+ company = frappe.get_doc("Company", get_company_for_dashboards())
+ bank_account = company.default_bank_account or get_account_name("Bank", company=company.name)
+ fiscal_year = get_fiscal_year(date=nowdate())
+ 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": "Date Range",
+ "period_start_date": get_date_str(fiscal_year[1]),
+ "period_end_date": get_date_str(fiscal_year[2]),
+ "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[0],
+ "to_fiscal_year": fiscal_year[0],
+ "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 = get_fiscal_year(date=nowdate())
+ year_start_date = get_date_str(fiscal_year[1])
+ year_end_date = get_date_str(fiscal_year[2])
+ 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/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index b0210e5..b57e678 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -185,7 +185,7 @@
total_days, total_booking_days, account_currency)
make_gl_entries(doc, credit_account, debit_account, against,
- amount, base_amount, end_date, project, account_currency, item.cost_center, item.name, deferred_process)
+ amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process)
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
if frappe.flags.deferred_accounting_error:
@@ -222,7 +222,7 @@
doc.submit()
def make_gl_entries(doc, credit_account, debit_account, against,
- amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no, deferred_process=None):
+ amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):
# GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries
@@ -236,12 +236,12 @@
"credit": base_amount,
"credit_in_account_currency": amount,
"cost_center": cost_center,
- "voucher_detail_no": voucher_detail_no,
+ "voucher_detail_no": item.name,
'posting_date': posting_date,
'project': project,
'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process
- }, account_currency)
+ }, account_currency, item=item)
)
# GL Entry to debit the amount from the expense
gl_entries.append(
@@ -251,12 +251,12 @@
"debit": base_amount,
"debit_in_account_currency": amount,
"cost_center": cost_center,
- "voucher_detail_no": voucher_detail_no,
+ "voucher_detail_no": item.name,
'posting_date': posting_date,
'project': project,
'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process
- }, account_currency)
+ }, account_currency, item=item)
)
if gl_entries:
diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json
index 0d6aca6..576d10c 100644
--- a/erpnext/accounts/desk_page/accounting/accounting.json
+++ b/erpnext/accounts/desk_page/accounting/accounting.json
@@ -47,11 +47,6 @@
},
{
"hidden": 0,
- "links": "[\n {\n \"description\": \"Match non-linked Invoices and Payments.\",\n \"label\": \"Match Payments with Invoices\",\n \"name\": \"Payment Reconciliation\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update bank payment dates with journals.\",\n \"label\": \"Update Bank Clearance Dates\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Invoice Discounting\",\n \"name\": \"Invoice Discounting\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Clearance Summary\",\n \"name\": \"Bank Clearance Summary\",\n \"type\": \"report\"\n },\n {\n \"label\": \"Bank Guarantee\",\n \"name\": \"Bank Guarantee\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup cheque dimensions for printing\",\n \"label\": \"Cheque Print Template\",\n \"name\": \"Cheque Print Template\",\n \"type\": \"doctype\"\n }\n]",
- "title": "Banking and Payments"
- },
- {
- "hidden": 0,
"label": "Subscription Management",
"links": "[\n {\n \"label\": \"Subscription Plan\",\n \"name\": \"Subscription Plan\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Subscription\",\n \"name\": \"Subscription\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Subscription Settings\",\n \"name\": \"Subscription Settings\",\n \"type\": \"doctype\"\n }\n]"
},
@@ -89,8 +84,8 @@
"category": "Modules",
"charts": [
{
- "chart_name": "Bank Balance",
- "label": "Bank Balance"
+ "chart_name": "Profit and Loss",
+ "label": "Profit and Loss"
}
],
"creation": "2020-03-02 15:41:59.515192",
@@ -99,24 +94,39 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
- "icon": "",
"idx": 0,
"is_standard": 1,
"label": "Accounting",
- "modified": "2020-04-29 12:17:34.844397",
+ "modified": "2020-05-18 17:27:26.882340",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
+ "onboarding": "Accounts",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
- "label": "Account",
+ "label": "Chart Of Accounts",
"link_to": "Account",
"type": "DocType"
},
{
+ "label": "Sales Invoice",
+ "link_to": "Sales Invoice",
+ "type": "DocType"
+ },
+ {
+ "label": "Purchase Invoice",
+ "link_to": "Purchase Invoice",
+ "type": "DocType"
+ },
+ {
+ "label": "Dashboard",
+ "link_to": "Accounts",
+ "type": "Dashboard"
+ },
+ {
"label": "Journal Entry",
"link_to": "Journal Entry",
"type": "DocType"
@@ -137,11 +147,6 @@
"type": "Report"
},
{
- "label": "Profit and Loss Statement",
- "link_to": "Profit and Loss Statement",
- "type": "Report"
- },
- {
"label": "Trial Balance",
"link_to": "Trial Balance",
"type": "Report"
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 7a85bfb..894ec5b 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -162,9 +162,9 @@
def get_doctypes_with_dimensions():
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
- "Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item",
- "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item",
- "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
+ "Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
+ "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
+ "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"]
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index efab580..291aff3 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -112,8 +112,8 @@
from tabAccount where name=%s""", self.account, as_dict=1)[0]
if ret.is_group==1:
- frappe.throw(_("{0} {1}: Account {2} cannot be a Group")
- .format(self.voucher_type, self.voucher_no, self.account))
+ frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in
+ transactions''').format(self.voucher_type, self.voucher_no, self.account))
if ret.docstatus==2:
frappe.throw(_("{0} {1}: Account {2} is inactive")
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
index 39fc203..594b4d4 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
@@ -8,6 +8,7 @@
from frappe.utils import flt, getdate, nowdate, add_days
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import make_gl_entries
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class InvoiceDiscounting(AccountsController):
def validate(self):
@@ -81,10 +82,15 @@
def make_gl_entries(self):
company_currency = frappe.get_cached_value('Company', self.company, "default_currency")
+
gl_entries = []
+ invoice_fields = ["debit_to", "party_account_currency", "conversion_rate", "cost_center"]
+ accounting_dimensions = get_accounting_dimensions()
+
+ invoice_fields.extend(accounting_dimensions)
+
for d in self.invoices:
- inv = frappe.db.get_value("Sales Invoice", d.sales_invoice,
- ["debit_to", "party_account_currency", "conversion_rate", "cost_center"], as_dict=1)
+ inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, invoice_fields, as_dict=1)
if d.outstanding_amount:
outstanding_in_company_currency = flt(d.outstanding_amount * inv.conversion_rate,
@@ -102,7 +108,7 @@
"cost_center": inv.cost_center,
"against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice"
- }, inv.party_account_currency))
+ }, inv.party_account_currency, item=inv))
gl_entries.append(self.get_gl_dict({
"account": self.accounts_receivable_credit,
@@ -115,7 +121,7 @@
"cost_center": inv.cost_center,
"against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice"
- }, ar_credit_account_currency))
+ }, ar_credit_account_currency, item=inv))
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 83c670e..d2245d6 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -86,7 +86,7 @@
self.update_payment_schedule(cancel=1)
self.set_payment_req_status()
self.set_status()
-
+
def set_payment_req_status(self):
from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status
update_payment_req_status(self, None)
@@ -280,7 +280,7 @@
outstanding_amount, is_return = frappe.get_cached_value(d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"])
if outstanding_amount <= 0 and not is_return:
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
-
+
for k, v in no_oustanding_refs.items():
frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\
If this is undesirable please cancel the corresponding Payment Entry.")
@@ -451,8 +451,6 @@
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
def set_remarks(self):
- if self.remarks: return
-
if self.payment_type=="Internal Transfer":
remarks = [_("Amount {0} {1} transferred from {2} to {3}")
.format(self.paid_from_account_currency, self.paid_amount, self.paid_from, self.paid_to)]
@@ -506,7 +504,7 @@
"against": against_account,
"account_currency": self.party_account_currency,
"cost_center": self.cost_center
- })
+ }, item=self)
dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit"
@@ -550,7 +548,7 @@
"credit_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount,
"cost_center": self.cost_center
- })
+ }, item=self)
)
if self.payment_type in ("Receive", "Internal Transfer"):
gl_entries.append(
@@ -561,7 +559,7 @@
"debit_in_account_currency": self.received_amount,
"debit": self.base_received_amount,
"cost_center": self.cost_center
- })
+ }, item=self)
)
def add_deductions_gl_entries(self, gl_entries):
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 19f571f..4d9053a 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -99,7 +99,7 @@
self.same_item = 1
def validate_max_discount(self):
- if self.rate_or_discount == "Discount Percentage" and self.items:
+ if self.rate_or_discount == "Discount Percentage" and self.get("items"):
for d in self.items:
max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount")
if max_discount and flt(self.discount_percentage) > flt(max_discount):
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index b358f56..cb05481 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -4,13 +4,19 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe, copy, json
-from frappe import throw, _
+
+import copy
+import json
+
from six import string_types
-from frappe.utils import flt, cint, get_datetime, get_link_to_form, today
+
+import frappe
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor
+from frappe import _, throw
+from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today
+
class MultiplePricingRuleConflict(frappe.ValidationError): pass
@@ -502,18 +508,16 @@
return list(set(apply_on_data))
def validate_coupon_code(coupon_name):
- from frappe.utils import today,getdate
- coupon=frappe.get_doc("Coupon Code",coupon_name)
+ coupon = frappe.get_doc("Coupon Code", coupon_name)
+
if coupon.valid_from:
- if coupon.valid_from > getdate(today()) :
- frappe.throw(_("Sorry,coupon code validity has not started"))
+ if coupon.valid_from > getdate(today()):
+ frappe.throw(_("Sorry, this coupon code's validity has not started"))
elif coupon.valid_upto:
- if coupon.valid_upto < getdate(today()) :
- frappe.throw(_("Sorry,coupon code validity has expired"))
- elif coupon.used>=coupon.maximum_use:
- frappe.throw(_("Sorry,coupon code are exhausted"))
- else:
- return
+ if coupon.valid_upto < getdate(today()):
+ frappe.throw(_("Sorry, this coupon code's validity has expired"))
+ elif coupon.used >= coupon.maximum_use:
+ frappe.throw(_("Sorry, this coupon code is no longer valid"))
def update_coupon_code_count(coupon_name,transaction_type):
coupon=frappe.get_doc("Coupon Code",coupon_name)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 3aa24df..aa1d5b5 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -460,7 +460,7 @@
"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
- }, self.party_account_currency)
+ }, self.party_account_currency, item=self)
)
def make_item_gl_entries(self, gl_entries):
@@ -841,7 +841,7 @@
"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
- }, self.party_account_currency)
+ }, self.party_account_currency, item=self)
)
gl_entries.append(
@@ -852,7 +852,7 @@
"credit_in_account_currency": self.base_paid_amount \
if bank_account_currency==self.company_currency else self.paid_amount,
"cost_center": self.cost_center
- }, bank_account_currency)
+ }, bank_account_currency, item=self)
)
def make_write_off_gl_entry(self, gl_entries):
@@ -873,7 +873,7 @@
"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
- }, self.party_account_currency)
+ }, self.party_account_currency, item=self)
)
gl_entries.append(
self.get_gl_dict({
@@ -883,7 +883,7 @@
"credit_in_account_currency": self.base_write_off_amount \
if write_off_account_currency==self.company_currency else self.write_off_amount,
"cost_center": self.cost_center or self.write_off_cost_center
- })
+ }, item=self)
)
def make_gle_for_rounding_adjustment(self, gl_entries):
@@ -902,8 +902,7 @@
"debit_in_account_currency": self.rounding_adjustment,
"debit": self.base_rounding_adjustment,
"cost_center": self.cost_center or round_off_cost_center,
- }
- ))
+ }, item=self))
def on_cancel(self):
super(PurchaseInvoice, self).on_cancel()
@@ -1021,6 +1020,40 @@
# calculate totals again after applying TDS
self.calculate_taxes_and_totals()
+
+ def set_status(self, update=False, status=None, update_modified=True):
+ if self.is_new():
+ if self.get('amended_from'):
+ self.status = 'Draft'
+ return
+
+ precision = self.precision("outstanding_amount")
+ outstanding_amount = flt(self.outstanding_amount, precision)
+ due_date = getdate(self.due_date)
+ nowdate = getdate()
+
+ if not status:
+ if self.docstatus == 2:
+ status = "Cancelled"
+ elif self.docstatus == 1:
+ if outstanding_amount > 0 and due_date < nowdate:
+ self.status = "Overdue"
+ elif outstanding_amount > 0 and due_date >= nowdate:
+ self.status = "Unpaid"
+ #Check if outstanding amount is 0 due to debit note issued against invoice
+ elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
+ self.status = "Debit Note Issued"
+ elif self.is_return == 1:
+ self.status = "Return"
+ elif 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 get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index e41ad42..6170005 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -86,6 +86,8 @@
pe.submit()
pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name)
+ pi_doc.load_from_db()
+ self.assertTrue(pi_doc.status, "Paid")
self.assertRaises(frappe.LinkExistsError, pi_doc.cancel)
unlink_payment_on_cancel_of_invoice()
@@ -203,7 +205,9 @@
pi.insert()
pi.submit()
+ pi.load_from_db()
+ self.assertTrue(pi.status, "Unpaid")
self.check_gle_for_pi(pi.name)
def check_gle_for_pi(self, pi):
@@ -234,6 +238,9 @@
pi = frappe.copy_doc(test_records[0])
pi.insert()
+ pi.load_from_db()
+
+ self.assertTrue(pi.status, "Draft")
pi.naming_series = 'TEST-'
self.assertRaises(frappe.CannotChangeConstantError, pi.save)
@@ -248,6 +255,8 @@
pi.get("taxes").pop(1)
pi.insert()
pi.submit()
+ pi.load_from_db()
+ self.assertTrue(pi.status, "Unpaid")
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
@@ -599,6 +608,11 @@
# return entry
pi1 = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, rate=50, update_stock=1)
+ pi.load_from_db()
+ self.assertTrue(pi.status, "Debit Note Issued")
+ pi1.load_from_db()
+ self.assertTrue(pi1.status, "Return")
+
actual_qty_2 = get_qty_after_transaction()
self.assertEqual(actual_qty_1 - 2, actual_qty_2)
@@ -771,6 +785,8 @@
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import get_outstanding_amount
pi = make_purchase_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1)
+ pi.load_from_db()
+ self.assertTrue(pi.status, "Return")
outstanding_amount = get_outstanding_amount(pi.doctype,
pi.name, "Creditors - _TC", pi.supplier, "Supplier")
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index f248276..df0c3d2 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -924,7 +924,7 @@
if(patient && patient!=selected_patient){
selected_patient = patient;
var method = "erpnext.healthcare.utils.get_healthcare_services_to_invoice";
- var args = {patient: patient};
+ var args = {patient: patient, company: frm.doc.company};
var columns = (["service", "reference_name", "reference_type"]);
get_healthcare_items(frm, true, $results, $placeholder, method, args, columns);
}
@@ -1068,7 +1068,11 @@
description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.',
get_query: function(doc) {
return {
- filters: { patient: dialog.get_value("patient"), docstatus: 1 }
+ filters: {
+ patient: dialog.get_value("patient"),
+ company: frm.doc.company,
+ docstatus: 1
+ }
};
}
},
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index db20589..63c34ed 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -398,7 +398,7 @@
{
"allow_on_submit": 1,
"fieldname": "po_no",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"label": "Customer's Purchase Order",
"no_copy": 1,
"print_hide": 1
@@ -1579,7 +1579,7 @@
"idx": 181,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-29 13:37:09.355300",
+ "modified": "2020-05-19 17:00:57.208696",
"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 3b0fade..05b85da 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -791,7 +791,7 @@
"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
- }, self.party_account_currency)
+ }, self.party_account_currency, item=self)
)
def make_tax_gl_entries(self, gl_entries):
@@ -808,7 +808,7 @@
tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else
flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))),
"cost_center": tax.cost_center
- }, account_currency)
+ }, account_currency, item=tax)
)
def make_item_gl_entries(self, gl_entries):
@@ -828,7 +828,7 @@
for gle in fixed_asset_gl_entries:
gle["against"] = self.customer
- gl_entries.append(self.get_gl_dict(gle))
+ gl_entries.append(self.get_gl_dict(gle, item=item))
asset.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None)
@@ -866,7 +866,7 @@
"against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
- })
+ }, item=self)
)
gl_entries.append(
self.get_gl_dict({
@@ -875,7 +875,7 @@
"against": self.customer,
"debit": self.loyalty_amount,
"remark": "Loyalty Points redeemed by the customer"
- })
+ }, item=self)
)
def make_pos_gl_entries(self, gl_entries):
@@ -896,7 +896,7 @@
"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
- }, self.party_account_currency)
+ }, self.party_account_currency, item=self)
)
payment_mode_account_currency = get_account_currency(payment_mode.account)
@@ -909,7 +909,7 @@
if payment_mode_account_currency==self.company_currency \
else payment_mode.amount,
"cost_center": self.cost_center
- }, payment_mode_account_currency)
+ }, payment_mode_account_currency, item=self)
)
def make_gle_for_change_amount(self, gl_entries):
@@ -927,7 +927,7 @@
"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
- }, self.party_account_currency)
+ }, self.party_account_currency, item=self)
)
gl_entries.append(
@@ -936,7 +936,7 @@
"against": self.customer,
"credit": self.base_change_amount,
"cost_center": self.cost_center
- })
+ }, item=self)
)
else:
frappe.throw(_("Select change amount account"), title="Mandatory Field")
@@ -960,7 +960,7 @@
"against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
- }, self.party_account_currency)
+ }, self.party_account_currency, item=self)
)
gl_entries.append(
self.get_gl_dict({
@@ -971,7 +971,7 @@
self.precision("base_write_off_amount")) if write_off_account_currency==self.company_currency
else flt(self.write_off_amount, self.precision("write_off_amount"))),
"cost_center": self.cost_center or self.write_off_cost_center or default_cost_center
- }, write_off_account_currency)
+ }, write_off_account_currency, item=self)
)
def make_gle_for_rounding_adjustment(self, gl_entries):
@@ -988,8 +988,7 @@
"credit": flt(self.base_rounding_adjustment,
self.precision("base_rounding_adjustment")),
"cost_center": self.cost_center or round_off_cost_center,
- }
- ))
+ }, item=self))
def update_billing_status_in_dn(self, update_modified=True):
updated_delivery_notes = []
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index dd6b4fd..4d43919 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -58,7 +58,7 @@
"rate": tax_rate_detail.tax_withholding_rate,
"threshold": tax_rate_detail.single_threshold,
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
- "description": tax_withholding.category_name
+ "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category
})
def get_tax_withholding_rates(tax_withholding, fiscal_year):
diff --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json
new file mode 100644
index 0000000..12da440
--- /dev/null
+++ b/erpnext/accounts/module_onboarding/accounts/accounts.json
@@ -0,0 +1,51 @@
+{
+ "allow_roles": [
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Accounts User"
+ }
+ ],
+ "creation": "2020-05-13 19:03:32.564049",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "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_by": "Administrator",
+ "module": "Accounts",
+ "name": "Accounts",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Chart Of Accounts"
+ },
+ {
+ "step": "Setup Taxes"
+ },
+ {
+ "step": "Create a Product"
+ },
+ {
+ "step": "Create a Supplier"
+ },
+ {
+ "step": "Create Your First Purchase Invoice"
+ },
+ {
+ "step": "Create a Customer"
+ },
+ {
+ "step": "Create Your First Sales Invoice"
+ },
+ {
+ "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
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json
new file mode 100644
index 0000000..cbd022b
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json
@@ -0,0 +1,20 @@
+{
+ "action": "Go to Page",
+ "creation": "2020-05-13 19:58:20.928127",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:40:28.410447",
+ "modified_by": "Administrator",
+ "name": "Chart Of Accounts",
+ "owner": "Administrator",
+ "path": "Tree/Account",
+ "reference_document": "Account",
+ "show_full_form": 0,
+ "title": "Review Chart Of Accounts",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json b/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json
new file mode 100644
index 0000000..c8be357
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 17:53:00.876946",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-14 18:06:25.212923",
+ "modified_by": "Administrator",
+ "name": "Configure Account Settings",
+ "owner": "Administrator",
+ "reference_document": "Accounts Settings",
+ "show_full_form": 1,
+ "title": "Configure Account Settings",
+ "validate_action": 1
+}
\ 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
new file mode 100644
index 0000000..bb396d2
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 17:46:41.831517",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:46:41.831517",
+ "modified_by": "Administrator",
+ "name": "Create a Customer",
+ "owner": "Administrator",
+ "reference_document": "Customer",
+ "show_full_form": 0,
+ "title": "Create a Customer",
+ "validate_action": 1
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..450bee1
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 17:45:28.554605",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:45:28.554605",
+ "modified_by": "Administrator",
+ "name": "Create a Product",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create a Product",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json
new file mode 100644
index 0000000..7a64224
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 22:09:10.043554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 22:09:10.043554",
+ "modified_by": "Administrator",
+ "name": "Create a Supplier",
+ "owner": "Administrator",
+ "reference_document": "Supplier",
+ "show_full_form": 0,
+ "title": "Create a Supplier",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json
new file mode 100644
index 0000000..3a2b8d3
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 22:10:07.049704",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 22:10:07.049704",
+ "modified_by": "Administrator",
+ "name": "Create Your First Purchase Invoice",
+ "owner": "Administrator",
+ "reference_document": "Purchase Invoice",
+ "show_full_form": 1,
+ "title": "Create Your First Purchase Invoice ",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json b/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json
new file mode 100644
index 0000000..473de50
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 17:48:21.019019",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:48:21.019019",
+ "modified_by": "Administrator",
+ "name": "Create Your First Sales Invoice",
+ "owner": "Administrator",
+ "reference_document": "Sales Invoice",
+ "show_full_form": 1,
+ "title": "Create Your First Sales Invoice ",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json
new file mode 100644
index 0000000..8e00067
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-13 19:29:43.844463",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:40:16.014413",
+ "modified_by": "Administrator",
+ "name": "Setup Taxes",
+ "owner": "Administrator",
+ "reference_document": "Sales Taxes and Charges Template",
+ "show_full_form": 1,
+ "title": "Lets create a Tax Template for Sales ",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index e9c286f..a0a1b97 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -546,7 +546,7 @@
self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4 = 30, 60, 90, 120
for i, days in enumerate([self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4]):
- if row.age <= days:
+ if cint(row.age) <= cint(days):
index = i
break
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
index 49c1d0f..05dc282 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
@@ -56,14 +56,26 @@
row += totals
data.append(row)
- return columns, data
+ chart = get_chart_data(filters, columns, data)
+ return columns, data, None, chart
def get_columns(filters):
columns = [
- _(filters.get("budget_against"))
- + ":Link/%s:150" % (filters.get("budget_against")),
- _("Account") + ":Link/Account:150"
+ {
+ 'label': _(filters.get("budget_against")),
+ 'fieldtype': 'Link',
+ 'fieldname': 'budget_against',
+ 'options': filters.get('budget_against'),
+ 'width': 150
+ },
+ {
+ 'label': _('Account'),
+ 'fieldname': 'Account',
+ 'fieldtype': 'Link',
+ 'options': 'Account',
+ 'width': 150
+ }
]
group_months = False if filters["period"] == "Monthly" else True
@@ -79,7 +91,12 @@
_("Variance ") + " " + str(year[0])
]
for label in labels:
- columns.append(label + ":Float:150")
+ columns.append({
+ 'label': label,
+ 'fieldtype': 'Float',
+ 'fieldname': frappe.scrub(label),
+ 'width': 150
+ })
else:
for label in [
_("Budget") + " (%s)" + " " + str(year[0]),
@@ -95,14 +112,23 @@
else:
label = label % formatdate(from_date, format_string="MMM")
- columns.append(label + ":Float:150")
+ columns.append({
+ 'label': label,
+ 'fieldtype': 'Float',
+ 'fieldname': frappe.scrub(label),
+ 'width': 150
+ })
if filters["period"] != "Yearly":
- return columns + [
- _("Total Budget") + ":Float:150",
- _("Total Actual") + ":Float:150",
- _("Total Variance") + ":Float:150"
- ]
+ for label in [_("Total Budget"), _("Total Actual"), _("Total Variance")]:
+ columns.append({
+ 'label': label,
+ 'fieldtype': 'Float',
+ 'fieldname': frappe.scrub(label),
+ 'width': 150
+ })
+
+ return columns
else:
return columns
@@ -173,7 +199,7 @@
filters.budget_against,
filters.company,
]
- + filters.get("budget_against_filter")
+ + (filters.get("budget_against_filter") or [])
), as_dict=True)
@@ -305,3 +331,49 @@
})
return fiscal_year
+
+def get_chart_data(filters, columns, data):
+
+ if not data:
+ return None
+
+ labels = []
+
+ fiscal_year = get_fiscal_years(filters)
+ group_months = False if filters["period"] == "Monthly" else True
+
+ for year in fiscal_year:
+ for from_date, to_date in get_period_date_ranges(filters["period"], year[0]):
+ if filters['period'] == 'Yearly':
+ labels.append(year[0])
+ else:
+ if group_months:
+ label = formatdate(from_date, format_string="MMM") + "-" \
+ + formatdate(to_date, format_string="MMM")
+ labels.append(label)
+ else:
+ label = formatdate(from_date, format_string="MMM")
+ labels.append(label)
+
+ no_of_columns = len(labels)
+
+ budget_values, actual_values = [0] * no_of_columns, [0] * no_of_columns
+ for d in data:
+ values = d[2:]
+ index = 0
+
+ for i in range(no_of_columns):
+ budget_values[i] += values[index]
+ actual_values[i] += values[index+1]
+ index += 3
+
+ return {
+ 'data': {
+ 'labels': labels,
+ 'datasets': [
+ {'name': 'Budget', 'chartType': 'bar', 'values': budget_values},
+ {'name': 'Actual Expense', 'chartType': 'bar', 'values': actual_values}
+ ]
+ }
+ }
+
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index 1188bea..2aecd6b 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -53,7 +53,7 @@
"label": __("Voucher No"),
"fieldtype": "Data",
on_change: function() {
- frappe.query_report.set_filter_value('group_by', "");
+ frappe.query_report.set_filter_value('group_by', "Group by Voucher (Consolidated)");
}
},
{
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 6afe208..f83a259 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -296,7 +296,7 @@
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
- if data[key].against_voucher:
+ if data[key].against_voucher and gle.against_voucher:
data[key].against_voucher += ', ' + gle.against_voucher
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js
index 622bab6..07752e1 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.js
+++ b/erpnext/accounts/report/trial_balance/trial_balance.js
@@ -46,7 +46,7 @@
"default": frappe.defaults.get_user_default("year_end_date"),
},
{
- "fieldname":"cost_center",
+ "fieldname": "cost_center",
"label": __("Cost Center"),
"fieldtype": "Link",
"options": "Cost Center",
@@ -61,7 +61,13 @@
}
},
{
- "fieldname":"finance_book",
+ "fieldname": "project",
+ "label": __("Project"),
+ "fieldtype": "Link",
+ "options": "Project"
+ },
+ {
+ "fieldname": "finance_book",
"label": __("Finance Book"),
"fieldtype": "Link",
"options": "Finance Book",
@@ -97,7 +103,7 @@
}
erpnext.dimension_filters.forEach((dimension) => {
- frappe.query_reports["Trial Balance"].filters.splice(5, 0 ,{
+ frappe.query_reports["Trial Balance"].filters.splice(6, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index d783241..8bd4399 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -69,6 +69,10 @@
gl_entries_by_account = {}
opening_balances = get_opening_balances(filters)
+
+ #add filter inside list so that the query in financial_statements.py doesn't break
+ filters.project = [filters.project]
+
set_gl_entries_by_account(filters.company, filters.from_date,
filters.to_date, min_lft, max_rgt, filters, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry))
@@ -102,6 +106,9 @@
additional_conditions += """ and cost_center in (select name from `tabCost Center`
where lft >= %s and rgt <= %s)""" % (lft, rgt)
+ if filters.project:
+ additional_conditions += " and project = %(project)s"
+
if filters.finance_book:
fb_conditions = " AND finance_book = %(finance_book)s"
if filters.include_default_book_entries:
@@ -116,6 +123,7 @@
"from_date": filters.from_date,
"report_type": report_type,
"year_start_date": filters.year_start_date,
+ "project": filters.project,
"finance_book": filters.finance_book,
"company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book')
}
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index a3200d5..505ba4c 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -125,7 +125,7 @@
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
frappe.throw(_("Available-for-use Date should be after purchase date"))
-
+
def validate_gross_and_purchase_amount(self):
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount:
frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\
@@ -455,7 +455,7 @@
for d in self.get('finance_books'):
if d.finance_book == self.default_finance_book:
return cint(d.idx) - 1
-
+
def validate_make_gl_entry(self):
purchase_document = self.get_purchase_document()
asset_bought_with_invoice = purchase_document == self.purchase_invoice
@@ -487,14 +487,14 @@
purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
return purchase_document
-
+
def get_asset_accounts(self):
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
asset_category = self.asset_category, company = self.company)
cwip_account = get_asset_account("capital_work_in_progress_account",
self.name, self.asset_category, self.company)
-
+
return fixed_asset_account, cwip_account
def make_gl_entries(self):
@@ -513,7 +513,7 @@
"credit": self.purchase_receipt_amount,
"credit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center
- }))
+ }, item=self))
gl_entries.append(self.get_gl_dict({
"account": fixed_asset_account,
@@ -523,7 +523,7 @@
"debit": self.purchase_receipt_amount,
"debit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center
- }))
+ }, item=self))
if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries
diff --git a/erpnext/assets/doctype/location/location.json b/erpnext/assets/doctype/location/location.json
index 6a35130..f56fd05 100644
--- a/erpnext/assets/doctype/location/location.json
+++ b/erpnext/assets/doctype/location/location.json
@@ -141,7 +141,7 @@
],
"is_tree": 1,
"links": [],
- "modified": "2020-03-18 18:00:08.885805",
+ "modified": "2020-05-08 16:11:11.375701",
"modified_by": "Administrator",
"module": "Assets",
"name": "Location",
@@ -221,7 +221,6 @@
}
],
"quick_entry": 1,
- "restrict_to_domain": "Agriculture",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
diff --git a/erpnext/buying/dashboard_fixtures.py b/erpnext/buying/dashboard_fixtures.py
new file mode 100644
index 0000000..0e2f78f
--- /dev/null
+++ b/erpnext/buying/dashboard_fixtures.py
@@ -0,0 +1,207 @@
+# 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": "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": "This Year Purchases"},
+ { "card": "Purchase Orders to Receive"},
+ { "card": "Purchase Orders to Bill"},
+ { "card": "Active Suppliers"}
+ ]
+ }]
+
+def get_charts():
+ 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():
+ return [
+ {
+ "name": "This Year Purchases",
+ "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],
+ ["Purchase Order", "transaction_date", "Between", [start_date,end_date], False]
+ ]),
+ "function": "Sum",
+ "is_public": 1,
+ "label": _("This Year Purchases"),
+ "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/desk_page/buying/buying.json b/erpnext/buying/desk_page/buying/buying.json
index 5e764cf..ee18545 100644
--- a/erpnext/buying/desk_page/buying/buying.json
+++ b/erpnext/buying/desk_page/buying/buying.json
@@ -2,18 +2,13 @@
"cards": [
{
"hidden": 0,
- "label": "Supplier",
- "links": "[\n {\n \"description\": \"Supplier database.\",\n \"label\": \"Supplier\",\n \"name\": \"Supplier\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Supplier Group master.\",\n \"label\": \"Supplier Group\",\n \"name\": \"Supplier Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Contacts.\",\n \"label\": \"Contact\",\n \"name\": \"Contact\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Addresses.\",\n \"label\": \"Address\",\n \"name\": \"Address\",\n \"type\": \"doctype\"\n }\n]"
+ "label": "Buying",
+ "links": "[ \n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Request for purchase.\",\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Purchase Orders given to Suppliers.\",\n \"label\": \"Purchase Order\",\n \"name\": \"Purchase Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Invoice\",\n \"name\": \"Purchase Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Request for quotation.\",\n \"label\": \"Request for Quotation\",\n \"name\": \"Request for Quotation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Quotations received from Suppliers.\",\n \"label\": \"Supplier Quotation\",\n \"name\": \"Supplier Quotation\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
- "label": "Purchasing",
- "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Purchase Orders given to Suppliers.\",\n \"label\": \"Purchase Order\",\n \"name\": \"Purchase Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Invoice\",\n \"name\": \"Purchase Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Request for purchase.\",\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Request for quotation.\",\n \"label\": \"Request for Quotation\",\n \"name\": \"Request for Quotation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Quotations received from Suppliers.\",\n \"label\": \"Supplier Quotation\",\n \"name\": \"Supplier Quotation\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Items and Pricing",
- "links": "[\n {\n \"description\": \"All Products or Services.\",\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Multiple Item prices.\",\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"onboard\": 1,\n \"route\": \"#Report/Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Price List master.\",\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bundle items at time of sale.\",\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of Item Groups.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying different promotional schemes.\",\n \"label\": \"Promotional Scheme\",\n \"name\": \"Promotional Scheme\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying pricing and discount.\",\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n }\n]"
+ "label": "Items & Pricing",
+ "links": "[\n {\n \"description\": \"All Products or Services.\",\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Multiple Item prices.\",\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"onboard\": 1,\n \"route\": \"#Report/Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Price List master.\",\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bundle items at time of sale.\",\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of Item Groups.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying different promotional schemes.\",\n \"label\": \"Promotional Scheme\",\n \"name\": \"Promotional Scheme\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying pricing and discount.\",\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@@ -22,73 +17,92 @@
},
{
"hidden": 0,
+ "label": "Supplier",
+ "links": "[\n {\n \"description\": \"Supplier database.\",\n \"label\": \"Supplier\",\n \"name\": \"Supplier\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Supplier Group master.\",\n \"label\": \"Supplier Group\",\n \"name\": \"Supplier Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Contacts.\",\n \"label\": \"Contact\",\n \"name\": \"Contact\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Addresses.\",\n \"label\": \"Address\",\n \"name\": \"Address\",\n \"type\": \"doctype\"\n }\n]"
+ },
+ {
+ "hidden": 0,
"label": "Supplier Scorecard",
"links": "[\n {\n \"description\": \"All Supplier scorecards.\",\n \"label\": \"Supplier Scorecard\",\n \"name\": \"Supplier Scorecard\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Templates of supplier scorecard variables.\",\n \"label\": \"Supplier Scorecard Variable\",\n \"name\": \"Supplier Scorecard Variable\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Templates of supplier scorecard criteria.\",\n \"label\": \"Supplier Scorecard Criteria\",\n \"name\": \"Supplier Scorecard Criteria\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Templates of supplier standings.\",\n \"label\": \"Supplier Scorecard Standing\",\n \"name\": \"Supplier Scorecard Standing\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Key Reports",
- "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Analytics\",\n \"name\": \"Purchase Analytics\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier-Wise Sales Analytics\",\n \"name\": \"Supplier-Wise Sales Analytics\",\n \"onboard\": 1,\n \"reference_doctype\": \"Stock Ledger Entry\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Order Trends\",\n \"name\": \"Purchase Order Trends\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Procurement Tracker\",\n \"name\": \"Procurement Tracker\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Requested Items To Be Ordered\",\n \"name\": \"Requested Items To Be Ordered\",\n \"onboard\": 1,\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n }\n]"
+ "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Analytics\",\n \"name\": \"Purchase Analytics\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Order Analysis\",\n \"name\": \"Purchase Order Analysis\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier-Wise Sales Analytics\",\n \"name\": \"Supplier-Wise Sales Analytics\",\n \"onboard\": 1,\n \"reference_doctype\": \"Stock Ledger Entry\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Requested Items to Order\",\n \"name\": \"Requested Items to Order\",\n \"onboard\": 1,\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Order Trends\",\n \"name\": \"Purchase Order Trends\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Procurement Tracker\",\n \"name\": \"Procurement Tracker\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
"label": "Other Reports",
- "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]"
+ "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]"
}
],
+ "cards_label": "Masters & Reports ",
"category": "Modules",
"charts": [
{
- "chart_name": "Expenses",
- "label": "Expenses"
+ "chart_name": "Purchase Order Trends",
+ "label": "Purchase Order Trends"
}
],
+ "charts_label": "Buying Dashboard",
"creation": "2020-01-28 11:50:26.195467",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Buying",
- "modified": "2020-04-01 11:28:51.192097",
+ "modified": "2020-05-19 19:44:36.260982",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",
+ "onboarding": "Buying",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
- "format": "{} Unpaid",
- "label": "Purchase Invoice",
- "link_to": "Purchase Invoice",
- "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"Unpaid\"\n}",
+ "color": "#cef6d1",
+ "format": "{} available",
+ "label": "Item",
+ "link_to": "Item",
+ "stats_filter": "{\n \"disabled\": 0\n}",
"type": "DocType"
},
{
- "format": "{} to receive",
+ "color": "#ffe8cd",
+ "format": "{} Pending",
+ "label": "Material Request",
+ "link_to": "Material Request",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"Pending\"\n}",
+ "type": "DocType"
+ },
+ {
+ "color": "#ffe8cd",
+ "format": "{} to Receive",
"label": "Purchase Order",
"link_to": "Purchase Order",
- "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Receive\"\n}",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\":[\"in\", [\"To Receive\", \"To Receive and Bill\"]]\n}",
"type": "DocType"
},
{
- "label": "Supplier Quotation",
- "link_to": "Supplier Quotation",
- "type": "DocType"
- },
- {
- "label": "Accounts Payable",
- "link_to": "Accounts Payable",
+ "label": "Purchase Analytics",
+ "link_to": "Purchase Analytics",
"type": "Report"
},
{
- "label": "Purchase Register",
- "link_to": "Purchase Register",
+ "label": "Purchase Order Analysis",
+ "link_to": "Purchase Order Analysis",
"type": "Report"
+ },
+ {
+ "label": "Buying Dashboard",
+ "link_to": "Buying",
+ "type": "Dashboard"
}
- ]
+ ],
+ "shortcuts_label": "Quick Access"
}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.js b/erpnext/buying/doctype/buying_settings/buying_settings.js
index 403b1c9..a27950a 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.js
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.js
@@ -6,3 +6,26 @@
// }
});
+
+frappe.tour['Buying Settings'] = [
+ {
+ fieldname: "supp_master_name",
+ title: "Supplier Naming By",
+ description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a set ") + "<a href='https://docs.erpnext.com/docs/user/manual/en/setting-up/settings/naming-series'>Naming Series</a>" + __(" choose the 'Naming Series' option."),
+ },
+ {
+ fieldname: "buying_price_list",
+ title: "Default Buying Price List",
+ description: __("Configure the default Price List when creating a new Buying transaction, the default is set as 'Standard Buying'. Item prices will be fetched from this Price List.")
+ },
+ {
+ fieldname: "po_required",
+ title: "Purchase Order Required for Purchase Invoice & Receipt Creation",
+ description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice or Receipt without creating a Purchase Order first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Order' checkbox in supplier master.")
+ },
+ {
+ fieldname: "pr_required",
+ title: "Purchase Receipt Required for Purchase Invoice Creation",
+ description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Receipt' checkbox in supplier master.")
+ }
+];
\ No newline at end of file
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index a492519..a0ab2a0 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -1,8 +1,10 @@
{
+ "actions": [],
"creation": "2013-06-25 11:04:03",
"description": "Settings for Buying Module",
"doctype": "DocType",
"document_type": "Other",
+ "engine": "InnoDB",
"field_order": [
"supp_master_name",
"supplier_group",
@@ -44,13 +46,13 @@
{
"fieldname": "po_required",
"fieldtype": "Select",
- "label": "Purchase Order Required",
+ "label": "Purchase Order Required for Purchase Invoice & Receipt Creation",
"options": "No\nYes"
},
{
"fieldname": "pr_required",
"fieldtype": "Select",
- "label": "Purchase Receipt Required",
+ "label": "Purchase Receipt Required for Purchase Invoice Creation",
"options": "No\nYes"
},
{
@@ -92,7 +94,8 @@
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
- "modified": "2019-08-20 13:13:09.055189",
+ "links": [],
+ "modified": "2020-05-15 14:49:32.513611",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
@@ -107,5 +110,7 @@
"share": 1,
"write": 1
}
- ]
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
index 16061c6..1b8b404 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
@@ -18,6 +18,10 @@
refresh: function() {
var me = this;
this._super();
+
+ if (this.frm.doc.__islocal && !this.frm.doc.valid_till) {
+ this.frm.set_value('valid_till', frappe.datetime.add_months(this.frm.doc.transaction_date, 1));
+ }
if (this.frm.doc.docstatus === 1) {
cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order,
__('Create'));
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index 82fc628..7db1516 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -13,9 +13,10 @@
"supplier",
"supplier_name",
"column_break1",
- "transaction_date",
- "amended_from",
"company",
+ "transaction_date",
+ "valid_till",
+ "amended_from",
"address_section",
"supplier_address",
"contact_person",
@@ -760,7 +761,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nSubmitted\nStopped\nCancelled",
+ "options": "\nDraft\nSubmitted\nStopped\nCancelled\nExpired",
"print_hide": 1,
"read_only": 1,
"reqd": 1,
@@ -791,13 +792,18 @@
"options": "Opportunity",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "valid_till",
+ "fieldtype": "Date",
+ "label": "Valid Till"
}
],
"icon": "fa fa-shopping-cart",
"idx": 29,
"is_submittable": 1,
"links": [],
- "modified": "2019-12-30 19:17:28.208693",
+ "modified": "2020-05-15 21:24:12.639482",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index 5b4356a..baf2457 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import flt, nowdate, add_days
+from frappe.utils import flt, nowdate, add_days, getdate
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.buying_controller import BuyingController
@@ -28,6 +28,7 @@
validate_for_items(self)
self.validate_with_previous_doc()
self.validate_uom_is_integer("uom", "qty")
+ self.validate_valid_till()
def on_submit(self):
frappe.db.set(self, "status", "Submitted")
@@ -52,6 +53,11 @@
"is_child_table": True
}
})
+
+ def validate_valid_till(self):
+ if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
+ frappe.throw(_("Valid till Date cannot be before Transaction Date"))
+
def update_rfq_supplier_status(self, include_me):
rfq_list = set([])
for item in self.items:
@@ -158,3 +164,11 @@
}, target_doc)
return doclist
+
+def set_expired_status():
+ frappe.db.sql("""
+ UPDATE
+ `tabSupplier Quotation` SET `status` = 'Expired'
+ WHERE
+ `status` not in ('Cancelled', 'Stopped') AND `valid_till` < %s
+ """, (nowdate()))
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js
index 9555439..9f4fece 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js
@@ -5,6 +5,8 @@
return [__("Ordered"), "green", "status,=,Ordered"];
} else if(doc.status==="Rejected") {
return [__("Lost"), "darkgrey", "status,=,Lost"];
+ } else if(doc.status==="Expired") {
+ return [__("Expired"), "darkgrey", "status,=,Expired"];
}
}
};
diff --git a/erpnext/buying/module_onboarding/buying/buying.json b/erpnext/buying/module_onboarding/buying/buying.json
new file mode 100644
index 0000000..8c798b3
--- /dev/null
+++ b/erpnext/buying/module_onboarding/buying/buying.json
@@ -0,0 +1,54 @@
+{
+ "allow_roles": [
+ {
+ "role": "Purchase Manager"
+ },
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Stock Manager"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-06 15:56:35.049205",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-19 20:03:55.776080",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Buying",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Introduction to Buying"
+ },
+ {
+ "step": "Create a Supplier"
+ },
+ {
+ "step": "Setup your Warehouse"
+ },
+ {
+ "step": "Create a Product"
+ },
+ {
+ "step": "Create a Material Request"
+ },
+ {
+ "step": "Create your first Purchase Order"
+ },
+ {
+ "step": "Buying Settings"
+ }
+ ],
+ "subtitle": "Products, Purchases, Analysis and more.",
+ "success_message": "The Buying Module is all set up!",
+ "title": "Let's Setup the Buying Module.",
+ "user_can_dismiss": 1
+}
\ 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
new file mode 100644
index 0000000..a788ccd
--- /dev/null
+++ b/erpnext/buying/onboarding_step/buying_settings/buying_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Update Settings",
+ "creation": "2020-05-06 15:53:44.667414",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "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": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json
new file mode 100644
index 0000000..9dc493d
--- /dev/null
+++ b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-15 14:39:09.818764",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-15 14:39:09.818764",
+ "modified_by": "Administrator",
+ "name": "Create a Material Request",
+ "owner": "Administrator",
+ "reference_document": "Material Request",
+ "show_full_form": 1,
+ "title": "Create a Material Request",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/create_a_product/create_a_product.json b/erpnext/buying/onboarding_step/create_a_product/create_a_product.json
new file mode 100644
index 0000000..d2068e1
--- /dev/null
+++ b/erpnext/buying/onboarding_step/create_a_product/create_a_product.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 18:16:06.624554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-12 18:30:02.489949",
+ "modified_by": "Administrator",
+ "name": "Create a Product",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create a Product",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/buying/onboarding_step/create_a_supplier/create_a_supplier.json
new file mode 100644
index 0000000..7a64224
--- /dev/null
+++ b/erpnext/buying/onboarding_step/create_a_supplier/create_a_supplier.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 22:09:10.043554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 22:09:10.043554",
+ "modified_by": "Administrator",
+ "name": "Create a Supplier",
+ "owner": "Administrator",
+ "reference_document": "Supplier",
+ "show_full_form": 0,
+ "title": "Create a Supplier",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json
new file mode 100644
index 0000000..9dbed23
--- /dev/null
+++ b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 18:17:49.976035",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-12 18:31:56.856112",
+ "modified_by": "Administrator",
+ "name": "Create your first Purchase Order",
+ "owner": "Administrator",
+ "reference_document": "Purchase Order",
+ "show_full_form": 0,
+ "title": "Create your first Purchase Order",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json
new file mode 100644
index 0000000..fd98fdd
--- /dev/null
+++ b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json
@@ -0,0 +1,19 @@
+{
+ "action": "Watch Video",
+ "creation": "2020-05-06 15:37:09.477765",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-12 18:25:08.509900",
+ "modified_by": "Administrator",
+ "name": "Introduction to Buying",
+ "owner": "Administrator",
+ "show_full_form": 0,
+ "title": "Introduction to Buying",
+ "validate_action": 1,
+ "video_url": "https://youtu.be/efFajTTQBa8"
+}
\ 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
new file mode 100644
index 0000000..557c905
--- /dev/null
+++ b/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
@@ -0,0 +1,20 @@
+{
+ "action": "Go to Page",
+ "creation": "2020-05-19 18:54:19.383397",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 18:54:19.383397",
+ "modified_by": "Administrator",
+ "name": "Setup your Warehouse",
+ "owner": "Administrator",
+ "path": "Tree/Warehouse",
+ "reference_document": "Warehouse",
+ "show_full_form": 0,
+ "title": "Setup your Warehouse",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/purchase_order_analysis/__init__.py b/erpnext/buying/report/purchase_order_analysis/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/buying/report/purchase_order_analysis/__init__.py
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
new file mode 100644
index 0000000..701da43
--- /dev/null
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
@@ -0,0 +1,78 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Purchase Order Analysis"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_default("company")
+ },
+ {
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "reqd": 1,
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "reqd": 1,
+ "default": frappe.datetime.get_today()
+ },
+ {
+ "fieldname": "purchase_order",
+ "label": __("Purchase Order"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Purchase Order",
+ "get_query": () =>{
+ return {
+ filters: { "docstatus": 1 }
+ }
+ }
+ },
+ {
+ "fieldname": "status",
+ "label": __("Status"),
+ "fieldtype": "MultiSelectList",
+ "width": "80",
+ get_data: function(txt) {
+ let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"]
+ let options = []
+ for (let option of status){
+ options.push({
+ "value": option,
+ "description": ""
+ })
+ }
+ return options
+ }
+ },
+ {
+ "fieldname": "group_by_po",
+ "label": __("Group by Purchase Order"),
+ "fieldtype": "Check",
+ "default": 0
+ }
+ ],
+
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+ let format_fields = ["received_qty", "billed_amount"];
+
+ if (in_list(format_fields, column.fieldname) && data && data[column.fieldname] > 0) {
+ value = "<span style='color:green'>" + value + "</span>";
+ }
+ return value;
+ }
+};
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.json b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.json
new file mode 100644
index 0000000..5ba3101
--- /dev/null
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.json
@@ -0,0 +1,33 @@
+{
+ "add_total_row": 1,
+ "creation": "2020-05-04 18:41:28.625119",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-05-15 20:57:52.623455",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Purchase Order Analysis",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Purchase Order",
+ "report_name": "Purchase Order Analysis",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Purchase Manager"
+ },
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Supplier"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
new file mode 100644
index 0000000..89be622
--- /dev/null
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
@@ -0,0 +1,271 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import copy
+from frappe import _
+from frappe.utils import flt, date_diff, getdate
+
+def execute(filters=None):
+ if not filters:
+ return [], []
+
+ validate_filters(filters)
+
+ columns = get_columns(filters)
+ conditions = get_conditions(filters)
+
+ data = get_data(conditions, filters)
+
+ if not data:
+ return [], [], None, []
+
+ data, chart_data = prepare_data(data, filters)
+
+ return columns, data, None, chart_data
+
+def validate_filters(filters):
+ from_date, to_date = filters.get("from_date"), filters.get("to_date")
+
+ if not from_date and to_date:
+ frappe.throw(_("From and To Dates are required."))
+ elif date_diff(to_date, from_date) < 0:
+ frappe.throw(_("To Date cannot be before From Date."))
+
+def get_conditions(filters):
+ conditions = ""
+ if filters.get("from_date") and filters.get("to_date"):
+ conditions += " and po.transaction_date between %(from_date)s and %(to_date)s"
+
+ if filters.get("company"):
+ conditions += " and po.company = %(company)s"
+
+ if filters.get("purchase_order"):
+ conditions += " and po.name = %(purchase_order)s"
+
+ if filters.get("status"):
+ conditions += " and po.status in %(status)s"
+
+ return conditions
+
+def get_data(conditions, filters):
+ data = frappe.db.sql("""
+ SELECT
+ po.transaction_date as date,
+ poi.schedule_date as required_date,
+ po.name as purchase_order,
+ po.status, po.supplier, poi.item_code,
+ poi.qty, poi.received_qty,
+ (poi.qty - poi.received_qty) AS pending_qty,
+ IFNULL(pii.qty, 0) as billed_qty,
+ poi.base_amount as amount,
+ (poi.received_qty * poi.base_rate) as received_qty_amount,
+ (poi.billed_amt * IFNULL(po.conversion_rate, 1)) as billed_amount,
+ (poi.base_amount - (poi.billed_amt * IFNULL(po.conversion_rate, 1))) as pending_amount,
+ po.set_warehouse as warehouse,
+ po.company, poi.name
+ FROM
+ `tabPurchase Order` po,
+ `tabPurchase Order Item` poi
+ LEFT JOIN `tabPurchase Invoice Item` pii
+ ON pii.po_detail = poi.name
+ WHERE
+ poi.parent = po.name
+ and po.status not in ('Stopped', 'Closed')
+ and po.docstatus = 1
+ {0}
+ GROUP BY poi.name
+ ORDER BY po.transaction_date ASC
+ """.format(conditions), filters, as_dict=1)
+
+ return data
+
+def prepare_data(data, filters):
+ completed, pending = 0, 0
+ pending_field = "pending_amount"
+ completed_field = "billed_amount"
+
+ if filters.get("group_by_po"):
+ purchase_order_map = {}
+
+ for row in data:
+ # sum data for chart
+ completed += row[completed_field]
+ pending += row[pending_field]
+
+ # prepare data for report view
+ row["qty_to_bill"] = flt(row["qty"]) - flt(row["billed_qty"])
+
+ if filters.get("group_by_po"):
+ po_name = row["purchase_order"]
+
+ if not po_name in purchase_order_map:
+ # create an entry
+ row_copy = copy.deepcopy(row)
+ purchase_order_map[po_name] = row_copy
+ else:
+ # update existing entry
+ po_row = purchase_order_map[po_name]
+ po_row["required_date"] = min(getdate(po_row["required_date"]), getdate(row["required_date"]))
+
+ # sum numeric columns
+ fields = ["qty", "received_qty", "pending_qty", "billed_qty", "qty_to_bill", "amount",
+ "received_qty_amount", "billed_amount", "pending_amount"]
+ for field in fields:
+ po_row[field] = flt(row[field]) + flt(po_row[field])
+
+ chart_data = prepare_chart_data(pending, completed)
+
+ if filters.get("group_by_po"):
+ data = []
+ for po in purchase_order_map:
+ data.append(purchase_order_map[po])
+ return data, chart_data
+
+ return data, chart_data
+
+def prepare_chart_data(pending, completed):
+ labels = ["Amount to Bill", "Billed Amount"]
+
+ return {
+ "data" : {
+ "labels": labels,
+ "datasets": [
+ {"values": [pending, completed]}
+ ]
+ },
+ "type": 'donut',
+ "height": 300
+ }
+
+def get_columns(filters):
+ columns = [
+ {
+ "label":_("Date"),
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "width": 90
+ },
+ {
+ "label":_("Required By"),
+ "fieldname": "required_date",
+ "fieldtype": "Date",
+ "width": 90
+ },
+ {
+ "label": _("Purchase Order"),
+ "fieldname": "purchase_order",
+ "fieldtype": "Link",
+ "options": "Purchase Order",
+ "width": 160
+ },
+ {
+ "label":_("Status"),
+ "fieldname": "status",
+ "fieldtype": "Data",
+ "width": 130
+ },
+ {
+ "label": _("Supplier"),
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "options": "Supplier",
+ "width": 130
+ }]
+
+ if not filters.get("group_by_po"):
+ columns.append({
+ "label":_("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100
+ })
+
+ columns.extend([
+ {
+ "label": _("Qty"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Received Qty"),
+ "fieldname": "received_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Pending Qty"),
+ "fieldname": "pending_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Billed Qty"),
+ "fieldname": "billed_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Qty to Bill"),
+ "fieldname": "qty_to_bill",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Amount"),
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "Company:company:default_currency",
+ "convertible": "rate"
+ },
+ {
+ "label": _("Billed Amount"),
+ "fieldname": "billed_amount",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "Company:company:default_currency",
+ "convertible": "rate"
+ },
+ {
+ "label": _("Pending Amount"),
+ "fieldname": "pending_amount",
+ "fieldtype": "Currency",
+ "width": 130,
+ "options": "Company:company:default_currency",
+ "convertible": "rate"
+ },
+ {
+ "label": _("Received Qty Amount"),
+ "fieldname": "received_qty_amount",
+ "fieldtype": "Currency",
+ "width": 130,
+ "options": "Company:company:default_currency",
+ "convertible": "rate"
+ },
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 100
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 100
+ }
+ ])
+
+ return columns
+
diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
index 888676c..1ed6cad 100644
--- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
+++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from erpnext.controllers.trends import get_columns,get_data
def execute(filters=None):
@@ -10,5 +11,48 @@
data = []
conditions = get_columns(filters, "Purchase Order")
data = get_data(filters, conditions)
+ chart_data = get_chart_data(data, conditions, filters)
- return conditions["columns"], data
\ No newline at end of file
+ return conditions["columns"], data, None, chart_data
+
+def get_chart_data(data, conditions, filters):
+ if not (data and conditions):
+ return []
+
+ datapoints = []
+
+ start = 2 if filters.get("based_on") in ["Item", "Supplier"] else 1
+ if filters.get("group_by"):
+ start += 1
+
+ # fetch only periodic columns as labels
+ columns = conditions.get("columns")[start:-2][1::2]
+ labels = [column.split(':')[0] for column in columns]
+ datapoints = [0] * len(labels)
+
+ for row in data:
+ # If group by filter, don't add first row of group (it's already summed)
+ if not row[start-1]:
+ continue
+ # Remove None values and compute only periodic data
+ row = [x if x else 0 for x in row[start:-2]]
+ row = row[1::2]
+
+ for i in range(len(row)):
+ datapoints[i] += row[i]
+
+ return {
+ "data" : {
+ "labels" : labels,
+ "datasets" : [
+ {
+ "name" : _("{0}").format(filters.get("period")) + _(" Purchase Value"),
+ "values" : datapoints
+ }
+ ]
+ },
+ "type" : "line",
+ "lineOptions": {
+ "regionFill": 1
+ }
+ }
\ No newline at end of file
diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js
index 3d05612..a76ffee 100644
--- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js
+++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js
@@ -5,20 +5,18 @@
filters: [
{
fieldtype: "Link",
- label: __("Supplier Quotation"),
- options: "Supplier Quotation",
- fieldname: "supplier_quotation",
- default: "",
- get_query: () => {
- return { filters: { "docstatus": ["<", 2] } }
- }
+ label: __("Company"),
+ options: "Company",
+ fieldname: "company",
+ default: frappe.defaults.get_user_default("Company"),
+ "reqd": 1
},
{
reqd: 1,
default: "",
options: "Item",
label: __("Item"),
- fieldname: "item",
+ fieldname: "item_code",
fieldtype: "Link",
get_query: () => {
let quote = frappe.query_report.get_filter_value('supplier_quotation');
@@ -37,8 +35,37 @@
}
}
}
+ },
+ {
+ fieldname: "supplier",
+ label: __("Supplier"),
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Supplier', txt);
+ }
+ },
+ {
+ fieldtype: "Link",
+ label: __("Supplier Quotation"),
+ options: "Supplier Quotation",
+ fieldname: "supplier_quotation",
+ default: "",
+ get_query: () => {
+ return { filters: { "docstatus": ["<", 2] } }
+ }
+ },
+ {
+ fieldtype: "Link",
+ label: __("Request for Quotation"),
+ options: "Request for Quotation",
+ fieldname: "request_for_quotation",
+ default: "",
+ get_query: () => {
+ return { filters: { "docstatus": ["<", 2] } }
+ }
}
],
+
onload: (report) => {
// Create a button for setting the default supplier
report.page.add_inner_button(__("Select Default Supplier"), () => {
@@ -102,6 +129,4 @@
});
dialog.show();
}
-}
-
-
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py
index 5aff6ba..a33867a 100644
--- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py
+++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py
@@ -2,103 +2,180 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-from erpnext.setup.utils import get_exchange_rate
-from frappe.utils import flt, cint
import frappe
+from frappe.utils import flt, cint
+from frappe import _
+from collections import defaultdict
+from erpnext.setup.utils import get_exchange_rate
def execute(filters=None):
- qty_list = get_quantity_list(filters.item)
- data = get_quote_list(filters.item, qty_list)
- columns = get_columns(qty_list)
- return columns, data
-
-def get_quote_list(item, qty_list):
- out = []
- if not item:
+ if not filters:
+ return [], []
+
+ conditions = get_conditions(filters)
+ supplier_quotation_data = get_data(filters, conditions)
+ columns = get_columns()
+
+ data, chart_data = prepare_data(supplier_quotation_data)
+
+ return columns, data, None, chart_data
+
+def get_conditions(filters):
+ conditions = ""
+ if filters.get("supplier_quotation"):
+ conditions += " AND sqi.parent = %(supplier_quotation)s"
+
+ if filters.get("request_for_quotation"):
+ conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s"
+
+ if filters.get("supplier"):
+ conditions += " AND sq.supplier in %(supplier)s"
+ return conditions
+
+def get_data(filters, conditions):
+ if not filters.get("item_code"):
return []
- suppliers = []
- price_data = []
- company_currency = frappe.db.get_default("currency")
- float_precision = cint(frappe.db.get_default("float_precision")) or 2
- # Get the list of suppliers
- for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item`
- where item_code=%s and docstatus < 2""", item, as_dict=1):
- for splr in frappe.db.sql("""select supplier from `tabSupplier Quotation`
- where name =%s and docstatus < 2""", root.parent, as_dict=1):
- ip = frappe._dict({
- "supplier": splr.supplier,
- "qty": root.qty,
- "parent": root.parent,
- "rate": root.rate
- })
- price_data.append(ip)
- suppliers.append(splr.supplier)
+ supplier_quotation_data = frappe.db.sql("""SELECT
+ sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
+ sq.supplier
+ FROM
+ `tabSupplier Quotation Item` sqi,
+ `tabSupplier Quotation` sq
+ WHERE
+ sqi.item_code = %(item_code)s
+ AND sqi.parent = sq.name
+ AND sqi.docstatus < 2
+ AND sq.company = %(company)s
+ AND sq.status != 'Expired'
+ {0}""".format(conditions), filters, as_dict=1)
- #Add a row for each supplier
- for root in set(suppliers):
- supplier_currency = frappe.db.get_value("Supplier", root, "default_currency")
+ return supplier_quotation_data
+
+def prepare_data(supplier_quotation_data):
+ out, suppliers, qty_list = [], [], []
+ supplier_wise_map = defaultdict(list)
+ supplier_qty_price_map = {}
+
+ company_currency = frappe.db.get_default("currency")
+ float_precision = cint(frappe.db.get_default("float_precision")) or 2
+
+ for data in supplier_quotation_data:
+ supplier = data.get("supplier")
+ supplier_currency = frappe.db.get_value("Supplier", data.get("supplier"), "default_currency")
+
if supplier_currency:
exchange_rate = get_exchange_rate(supplier_currency, company_currency)
else:
exchange_rate = 1
- row = frappe._dict({
- "supplier_name": root
- })
- for col in qty_list:
- # Get the quantity for this row
- for item_price in price_data:
- if str(item_price.qty) == col.key and item_price.supplier == root:
- row[col.key] = flt(item_price.rate * exchange_rate, float_precision)
- row[col.key + "QUOTE"] = item_price.parent
- break
- else:
- row[col.key] = ""
- row[col.key + "QUOTE"] = ""
- out.append(row)
-
- return out
-
-def get_quantity_list(item):
- out = []
-
- if item:
- qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item`
- where ifnull(item_code,'')=%s and docstatus < 2 order by qty""", item, as_dict=1)
+ row = {
+ "quotation": data.get("parent"),
+ "qty": data.get("qty"),
+ "price": flt(data.get("rate") * exchange_rate, float_precision),
+ "uom": data.get("uom"),
+ "request_for_quotation": data.get("request_for_quotation"),
+ }
- for qt in qty_list:
- col = frappe._dict({
- "key": str(qt.qty),
- "label": "Qty: " + str(int(qt.qty))
- })
- out.append(col)
+ # map for report view of form {'supplier1':[{},{},...]}
+ supplier_wise_map[supplier].append(row)
- return out
-
-def get_columns(qty_list):
+ # map for chart preparation of the form {'supplier1': {'qty': 'price'}}
+ if not supplier in supplier_qty_price_map:
+ supplier_qty_price_map[supplier] = {}
+ supplier_qty_price_map[supplier][row["qty"]] = row["price"]
+
+ suppliers.append(supplier)
+ qty_list.append(data.get("qty"))
+
+ suppliers = list(set(suppliers))
+ qty_list = list(set(qty_list))
+
+ # final data format for report view
+ for supplier in suppliers:
+ supplier_wise_map[supplier][0].update({"supplier_name": supplier})
+ for entry in supplier_wise_map[supplier]:
+ out.append(entry)
+
+ chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
+
+ return out, chart_data
+
+def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
+ data_points_map = {}
+ qty_list.sort()
+
+ # create qty wise values map of the form {'qty1':[value1, value2]}
+ for supplier in suppliers:
+ entry = supplier_qty_price_map[supplier]
+ for qty in qty_list:
+ if not qty in data_points_map:
+ data_points_map[qty] = []
+ if qty in entry:
+ data_points_map[qty].append(entry[qty])
+ else:
+ data_points_map[qty].append(None)
+
+ dataset = []
+ for qty in qty_list:
+ datapoints = {
+ "name": _("Price for Qty ") + str(qty),
+ "values": data_points_map[qty]
+ }
+ dataset.append(datapoints)
+
+ chart_data = {
+ "data": {
+ "labels": suppliers,
+ "datasets": dataset
+ },
+ "type": "bar"
+ }
+
+ return chart_data
+
+def get_columns():
columns = [{
"fieldname": "supplier_name",
- "label": "Supplier",
+ "label": _("Supplier"),
"fieldtype": "Link",
"options": "Supplier",
"width": 200
- }]
-
- for qty in qty_list:
- columns.append({
- "fieldname": qty.key,
- "label": qty.label,
- "fieldtype": "Currency",
- "options": "currency",
- "width": 80
- })
- columns.append({
- "fieldname": qty.key + "QUOTE",
- "label": "Quotation",
- "fieldtype": "Link",
- "options": "Supplier Quotation",
- "width": 90
- })
+ },
+ {
+ "fieldname": "quotation",
+ "label": _("Supplier Quotation"),
+ "fieldtype": "Link",
+ "options": "Supplier Quotation",
+ "width": 200
+ },
+ {
+ "fieldname": "qty",
+ "label": _("Quantity"),
+ "fieldtype": "Float",
+ "width": 80
+ },
+ {
+ "fieldname": "price",
+ "label": _("Price"),
+ "fieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "width": 110
+ },
+ {
+ "fieldname": "uom",
+ "label": _("UOM"),
+ "fieldtype": "Link",
+ "options": "UOM",
+ "width": 90
+ },
+ {
+ "fieldname": "request_for_quotation",
+ "label": _("Request for Quotation"),
+ "fieldtype": "Link",
+ "options": "Request for Quotation",
+ "width": 200
+ }
+ ]
return columns
\ 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/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/buying/report/requested_items_to_order/__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/requested_items_to_order.js
new file mode 100644
index 0000000..21adb13
--- /dev/null
+++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js
@@ -0,0 +1,64 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Requested Items to Order"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_default("company")
+ },
+ {
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "reqd": 1,
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "reqd": 1,
+ "default": frappe.datetime.get_today()
+ },
+ {
+ "fieldname": "material_request",
+ "label": __("Material Request"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Material Request",
+ "get_query": () =>{
+ return {
+ filters: {
+ "docstatus": 1,
+ "material_request_type": "Purchase",
+ "per_received": ["<", 100]
+ }
+ }
+ }
+ },
+ {
+ "fieldname": "group_by_mr",
+ "label": __("Group by Material Request"),
+ "fieldtype": "Check",
+ "default": 0
+ }
+ ],
+
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "ordered_qty" && data && data.ordered_qty > 0) {
+ value = "<span style='color:green'>" + value + "</span>";
+ }
+ return value;
+ }
+};
diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json
new file mode 100644
index 0000000..4a0578b
--- /dev/null
+++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json
@@ -0,0 +1,34 @@
+{
+ "add_total_row": 1,
+ "creation": "2020-05-04 20:23:57.750719",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-05-05 13:05:51.723951",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Requested Items to Order",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "",
+ "ref_doctype": "Material Request",
+ "report_name": "Requested Items to Order",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Purchase Manager"
+ },
+ {
+ "role": "Stock Manager"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Purchase User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py
new file mode 100644
index 0000000..a021d3c
--- /dev/null
+++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py
@@ -0,0 +1,211 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import copy
+from frappe import _
+from frappe.utils import flt, date_diff, getdate
+
+def execute(filters=None):
+ if not filters:
+ return [],[]
+
+ validate_filters(filters)
+
+ columns = get_columns(filters)
+ conditions = get_conditions(filters)
+
+ #get queried data
+ data = get_data(filters, conditions)
+
+ #prepare data for report and chart views
+ data, chart_data = prepare_data(data, filters)
+
+ return columns, data, None, chart_data
+
+def validate_filters(filters):
+ from_date, to_date = filters.get("from_date"), filters.get("to_date")
+
+ if not from_date and to_date:
+ frappe.throw(_("From and To Dates are required."))
+ elif date_diff(to_date, from_date) < 0:
+ frappe.throw(_("To Date cannot be before From Date."))
+
+def get_conditions(filters):
+ conditions = ''
+
+ if filters.get("from_date") and filters.get("to_date"):
+ conditions += " and mr.transaction_date between '{0}' and '{1}'".format(filters.get("from_date"),filters.get("to_date"))
+
+ if filters.get("company"):
+ conditions += " and mr.company = '{0}'".format(filters.get("company"))
+
+ if filters.get("material_request"):
+ conditions += " and mr.name = '{0}'".format(filters.get("material_request"))
+
+ return conditions
+
+def get_data(filters, conditions):
+ data = frappe.db.sql("""
+ select
+ mr.name as material_request,
+ mr.transaction_date as date,
+ mr_item.schedule_date as required_date,
+ mr_item.item_code as item_code,
+ 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,
+ mr_item.item_name as item_name,
+ mr.company as company
+ from
+ `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
+ where
+ mr_item.parent = mr.name
+ and mr.material_request_type = "Purchase"
+ and mr.docstatus = 1
+ and mr.status != "Stopped"
+ {conditions}
+ group by mr.name, mr_item.item_code
+ having
+ sum(ifnull(mr_item.ordered_qty, 0)) < sum(ifnull(mr_item.stock_qty, 0))
+ order by mr.transaction_date, mr.schedule_date""".format(conditions=conditions), as_dict=1)
+
+ return data
+
+def prepare_data(data, filters):
+ """Prepare consolidated Report data and Chart data"""
+ material_request_map = {}
+
+ for row in data:
+ if not row["material_request"] in material_request_map:
+ # create an entry with mr as key
+ row_copy = copy.deepcopy(row)
+ material_request_map[row["material_request"]] = row_copy
+ else:
+ mr_row = material_request_map[row["material_request"]]
+ mr_row["required_date"] = min(getdate(mr_row["required_date"]), getdate(row["required_date"]))
+
+ #sum numeric rows
+ fields = ["qty", "ordered_qty", "qty_to_order"]
+ for field in fields:
+ mr_row[field] = flt(mr_row[field]) + flt(row[field])
+
+ chart_data = prepare_chart_data(material_request_map)
+
+ if filters.get("group_by_mr"):
+ data =[]
+ for mr in material_request_map:
+ data.append(material_request_map[mr])
+ return data, chart_data
+
+ return data, chart_data
+
+def prepare_chart_data(data):
+ labels, qty_to_order, ordered_qty = [], [], []
+
+ for row in data:
+ mr_row = data[row]
+ labels.append(mr_row["material_request"])
+ qty_to_order.append(mr_row["qty_to_order"])
+ ordered_qty.append(mr_row["ordered_qty"])
+
+ chart_data = {
+ "data" : {
+ "labels": labels,
+ "datasets": [
+ {
+ 'name': _('Qty to Order'),
+ 'values': qty_to_order
+ },
+ {
+ 'name': _('Ordered Qty'),
+ 'values': ordered_qty
+ }
+ ]
+ },
+ "type": "bar",
+ "barOptions": {
+ "stacked": 1
+ },
+ }
+
+ return chart_data
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Material Request"),
+ "fieldname": "material_request",
+ "fieldtype": "Link",
+ "options": "Material Request",
+ "width": 150
+ },
+ {
+ "label":_("Date"),
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "width": 90
+ },
+ {
+ "label":_("Required By"),
+ "fieldname": "required_date",
+ "fieldtype": "Date",
+ "width": 100
+ }
+ ]
+
+ if not filters.get("group_by_mr"):
+ columns.extend([{
+ "label":_("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100
+ },
+ {
+ "label":_("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": _("UOM"),
+ "fieldname": "uom",
+ "fieldtype": "Data",
+ "width": 100,
+ }])
+
+ columns.extend([
+ {
+ "label": _("Qty"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Ordered Qty"),
+ "fieldname": "ordered_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Qty to Order"),
+ "fieldname": "qty_to_order",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 100
+ }
+ ])
+
+ return columns
diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py
index 1d40547..16b49a1 100644
--- a/erpnext/config/buying.py
+++ b/erpnext/config/buying.py
@@ -166,7 +166,7 @@
{
"type": "report",
"is_query_report": True,
- "name": "Requested Items To Be Ordered",
+ "name": "Requested Items To Order",
"reference_doctype": "Material Request",
"onboard": 1,
},
diff --git a/erpnext/config/manufacturing.py b/erpnext/config/manufacturing.py
index 2c18eeb..012f1ca 100644
--- a/erpnext/config/manufacturing.py
+++ b/erpnext/config/manufacturing.py
@@ -120,13 +120,7 @@
{
"type": "report",
"is_query_report": True,
- "name": "Open Work Orders",
- "doctype": "Work Order"
- },
- {
- "type": "report",
- "is_query_report": True,
- "name": "Work Orders in Progress",
+ "name": "Work Order Summary",
"doctype": "Work Order"
},
{
@@ -138,12 +132,6 @@
{
"type": "report",
"is_query_report": True,
- "name": "Completed Work Orders",
- "doctype": "Work Order"
- },
- {
- "type": "report",
- "is_query_report": True,
"name": "Production Analytics",
"doctype": "Work Order"
},
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 5febfd6..5fbc460 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -8,11 +8,14 @@
from frappe.utils import nowdate, getdate
from collections import defaultdict
from erpnext.stock.get_item_details import _get_item_tax_template
+from frappe.utils import unique
# searches for active employees
def employee_query(doctype, txt, searchfield, start, page_len, filters):
conditions = []
- return frappe.db.sql("""select name, employee_name from `tabEmployee`
+ fields = get_fields("Employee", ["name", "employee_name"])
+
+ return frappe.db.sql("""select {fields} from `tabEmployee`
where status = 'Active'
and docstatus < 2
and ({key} like %(txt)s
@@ -24,6 +27,7 @@
idx desc,
name, employee_name
limit %(start)s, %(page_len)s""".format(**{
+ 'fields': ", ".join(fields),
'key': searchfield,
'fcond': get_filters_cond(doctype, filters, conditions),
'mcond': get_match_cond(doctype)
@@ -34,9 +38,12 @@
'page_len': page_len
})
- # searches for leads which are not converted
+
+# searches for leads which are not converted
def lead_query(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql("""select name, lead_name, company_name from `tabLead`
+ fields = get_fields("Lead", ["name", "lead_name", "company_name"])
+
+ return frappe.db.sql("""select {fields} from `tabLead`
where docstatus < 2
and ifnull(status, '') != 'Converted'
and ({key} like %(txt)s
@@ -50,6 +57,7 @@
idx desc,
name, lead_name
limit %(start)s, %(page_len)s""".format(**{
+ 'fields': ", ".join(fields),
'key': searchfield,
'mcond':get_match_cond(doctype)
}), {
@@ -59,6 +67,7 @@
'page_len': page_len
})
+
# searches for customer
def customer_query(doctype, txt, searchfield, start, page_len, filters):
conditions = []
@@ -69,13 +78,9 @@
else:
fields = ["name", "customer_name", "customer_group", "territory"]
- meta = frappe.get_meta("Customer")
- searchfields = meta.get_search_fields()
- searchfields = searchfields + [f for f in [searchfield or "name", "customer_name"] \
- if not f in searchfields]
- fields = fields + [f for f in searchfields if not f in fields]
+ fields = get_fields("Customer", fields)
- fields = ", ".join(fields)
+ searchfields = frappe.get_meta("Customer").get_search_fields()
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
return frappe.db.sql("""select {fields} from `tabCustomer`
@@ -88,7 +93,7 @@
idx desc,
name, customer_name
limit %(start)s, %(page_len)s""".format(**{
- "fields": fields,
+ "fields": ", ".join(fields),
"scond": searchfields,
"mcond": get_match_cond(doctype),
"fcond": get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
@@ -99,6 +104,7 @@
'page_len': page_len
})
+
# searches for supplier
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
@@ -106,7 +112,8 @@
fields = ["name", "supplier_group"]
else:
fields = ["name", "supplier_name", "supplier_group"]
- fields = ", ".join(fields)
+
+ fields = get_fields("Supplier", fields)
return frappe.db.sql("""select {field} from `tabSupplier`
where docstatus < 2
@@ -119,7 +126,7 @@
idx desc,
name, supplier_name
limit %(start)s, %(page_len)s """.format(**{
- 'field': fields,
+ 'field': ', '.join(fields),
'key': searchfield,
'mcond':get_match_cond(doctype)
}), {
@@ -129,6 +136,7 @@
'page_len': page_len
})
+
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
company_currency = erpnext.get_company_currency(filters.get('company'))
@@ -153,6 +161,7 @@
return tax_accounts
+
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
conditions = []
@@ -221,10 +230,12 @@
"page_len": page_len
}, as_dict=as_dict)
+
def bom(doctype, txt, searchfield, start, page_len, filters):
conditions = []
+ fields = get_fields("BOM", ["name", "item"])
- return frappe.db.sql("""select tabBOM.name, tabBOM.item
+ return frappe.db.sql("""select {fields}
from tabBOM
where tabBOM.docstatus=1
and tabBOM.is_active=1
@@ -234,6 +245,7 @@
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
idx desc, name
limit %(start)s, %(page_len)s """.format(
+ fields=", ".join(fields),
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
mcond=get_match_cond(doctype).replace('%', '%%'),
key=searchfield),
@@ -244,13 +256,16 @@
'page_len': page_len or 20
})
+
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
cond = ''
if filters.get('customer'):
cond = """(`tabProject`.customer = %s or
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
- return frappe.db.sql("""select `tabProject`.name from `tabProject`
+ fields = get_fields("Project", ["name"])
+
+ return frappe.db.sql("""select {fields} from `tabProject`
where `tabProject`.status not in ("Completed", "Cancelled")
and {cond} `tabProject`.name like %(txt)s {match_cond}
order by
@@ -258,6 +273,7 @@
idx desc,
`tabProject`.name asc
limit {start}, {page_len}""".format(
+ fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]),
cond=cond,
match_cond=get_match_cond(doctype),
start=start,
@@ -268,8 +284,10 @@
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"])
+
return frappe.db.sql("""
- select `tabDelivery Note`.name, `tabDelivery Note`.customer, `tabDelivery Note`.posting_date
+ select %(fields)s
from `tabDelivery Note`
where `tabDelivery Note`.`%(key)s` like %(txt)s and
`tabDelivery Note`.docstatus = 1
@@ -284,6 +302,7 @@
)
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(start)s, %(page_len)s
""" % {
+ "fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]),
"key": searchfield,
"fcond": get_filters_cond(doctype, filters, []),
"mcond": get_match_cond(doctype),
@@ -349,6 +368,7 @@
order by expiry_date, name desc
limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args)
+
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
filter_list = []
@@ -371,6 +391,7 @@
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
@@ -385,6 +406,7 @@
company = frappe.db.escape(filters.get("company"))
))
+
@frappe.whitelist()
def get_income_account(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
@@ -490,6 +512,7 @@
return frappe.db.sql(query, filters)
+
@frappe.whitelist()
def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters):
item_filters = [
@@ -507,6 +530,7 @@
)
return item_manufacturers
+
@frappe.whitelist()
def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
query = """
@@ -520,6 +544,7 @@
return frappe.db.sql(query, filters)
+
@frappe.whitelist()
def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
query = """
@@ -533,6 +558,7 @@
return frappe.db.sql(query, filters)
+
@frappe.whitelist()
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
@@ -556,3 +582,13 @@
taxes = _get_item_tax_template(args, taxes, for_validate=True)
return [(d,) for d in set(taxes)]
+
+
+def get_fields(doctype, fields=[]):
+ meta = frappe.get_meta(doctype)
+ fields.extend(meta.get_search_fields())
+
+ if meta.title_field and not meta.title_field.strip() in fields:
+ fields.insert(1, meta.title_field.strip())
+
+ return unique(fields)
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index de76e45..b465a10 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -69,17 +69,6 @@
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
],
- "Purchase Invoice": [
- ["Draft", None],
- ["Submitted", "eval:self.docstatus==1"],
- ["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"],
- ["Return", "eval:self.is_return==1 and self.docstatus==1"],
- ["Debit Note Issued",
- "eval:self.outstanding_amount <= 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"],
- ["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"],
- ["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"],
- ["Cancelled", "eval:self.docstatus==2"],
- ],
"Material Request": [
["Draft", None],
["Stopped", "eval:self.status == 'Stopped'"],
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index 20ab51d..6fef0c4 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_events_in_timeline": 1,
"allow_import": 1,
"autoname": "naming_series:",
@@ -447,7 +448,7 @@
"idx": 5,
"image_field": "image",
"links": [],
- "modified": "2020-04-08 22:26:11.687110",
+ "modified": "2020-05-11 20:27:45.868960",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",
@@ -504,15 +505,6 @@
"read": 1,
"report": 1,
"role": "Sales User"
- },
- {
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Guest",
- "share": 1
}
],
"search_fields": "lead_name,lead_owner,status",
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index bdde9ee..377e061 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -15,7 +15,7 @@
params = urlencode({
"response_type":"code",
"client_id": self.consumer_key,
- "redirect_uri": get_site_url(frappe.local.site) + "/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?",
+ "redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(frappe.utils.get_url()),
"scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social"
})
@@ -30,7 +30,7 @@
"code": code,
"client_id": self.consumer_key,
"client_secret": self.get_password(fieldname="consumer_secret"),
- "redirect_uri": get_site_url(frappe.local.site) + "/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?",
+ "redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(frappe.utils.get_url()),
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.json b/erpnext/crm/doctype/twitter_settings/twitter_settings.json
index f92e7f0..36776e5 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.json
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.json
@@ -11,8 +11,8 @@
"consumer_key",
"column_break_5",
"consumer_secret",
- "oauth_token",
- "oauth_secret",
+ "access_token",
+ "access_token_secret",
"session_status"
],
"fields": [
@@ -42,20 +42,6 @@
"reqd": 1
},
{
- "fieldname": "oauth_token",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "OAuth Token",
- "read_only": 1
- },
- {
- "fieldname": "oauth_secret",
- "fieldtype": "Password",
- "hidden": 1,
- "label": "OAuth Token Secret",
- "read_only": 1
- },
- {
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
@@ -72,12 +58,26 @@
"label": "Session Status",
"options": "Expired\nActive",
"read_only": 1
+ },
+ {
+ "fieldname": "access_token",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Access Token",
+ "read_only": 1
+ },
+ {
+ "fieldname": "access_token_secret",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Access Token Secret",
+ "read_only": 1
}
],
"image_field": "profile_pic",
"issingle": 1,
"links": [],
- "modified": "2020-04-21 22:06:43.726798",
+ "modified": "2020-05-13 17:50:47.934776",
"modified_by": "Administrator",
"module": "CRM",
"name": "Twitter Settings",
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
index 7616b4c..976a23d 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
@@ -31,13 +31,13 @@
try:
auth.get_access_token(oauth_verifier)
- api = self.get_api()
+ api = self.get_api(auth.access_token, auth.access_token_secret)
user = api.me()
profile_pic = (user._json["profile_image_url"]).replace("_normal","")
frappe.db.set_value(self.doctype, self.name, {
- "oauth_token" : auth.access_token,
- "oauth_secret" : auth.access_token_secret,
+ "access_token" : auth.access_token,
+ "access_token_secret" : auth.access_token_secret,
"account_name" : user._json["screen_name"],
"profile_pic" : profile_pic,
"session_status" : "Active"
@@ -49,11 +49,11 @@
frappe.msgprint(_("Error! Failed to get access token."))
frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
- def get_api(self):
+ def get_api(self, access_token, access_token_secret):
# authentication of consumer key and secret
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
# authentication of access token and secret
- auth.set_access_token(self.oauth_token, self.get_password(fieldname="oauth_secret"))
+ auth.set_access_token(access_token, access_token_secret)
return tweepy.API(auth)
@@ -67,13 +67,13 @@
def upload_image(self, media):
media = get_file_path(media)
- api = self.get_api()
+ api = self.get_api(self.access_token, self.access_token_secret)
media = api.media_upload(media)
return media.media_id
def send_tweet(self, text, media_id=None):
- api = self.get_api()
+ api = self.get_api(self.access_token, self.access_token_secret)
try:
if media_id:
response = api.update_status(status = text, media_ids = [media_id])
diff --git a/erpnext/education/doctype/fees/fees.py b/erpnext/education/doctype/fees/fees.py
index f0d60fa..25d67d2 100644
--- a/erpnext/education/doctype/fees/fees.py
+++ b/erpnext/education/doctype/fees/fees.py
@@ -98,14 +98,16 @@
"debit_in_account_currency": self.grand_total,
"against_voucher": self.name,
"against_voucher_type": self.doctype
- })
+ }, item=self)
+
fee_gl_entry = self.get_gl_dict({
"account": self.income_account,
"against": self.student,
"credit": self.grand_total,
"credit_in_account_currency": self.grand_total,
"cost_center": self.cost_center
- })
+ }, item=self)
+
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries([student_gl_entries, fee_gl_entry], cancel=(self.docstatus == 2),
update_outstanding="Yes", merge_entries=False)
diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
index 6188652..44f87e0 100644
--- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
+++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
@@ -49,12 +49,13 @@
if event == "created":
sys_lang = frappe.get_single("System Settings").language or 'en'
raw_billing_data = order.get("billing")
+ raw_shipping_data = order.get("shipping")
customer_name = raw_billing_data.get("first_name") + " " + raw_billing_data.get("last_name")
- link_customer_and_address(raw_billing_data, customer_name)
+ link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name)
link_items(order.get("line_items"), woocommerce_settings, sys_lang)
create_sales_order(order, woocommerce_settings, customer_name, sys_lang)
-def link_customer_and_address(raw_billing_data, customer_name):
+def link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name):
customer_woo_com_email = raw_billing_data.get("email")
customer_exists = frappe.get_value("Customer", {"woocommerce_email": customer_woo_com_email})
if not customer_exists:
@@ -68,38 +69,74 @@
customer.customer_name = customer_name
customer.woocommerce_email = customer_woo_com_email
customer.flags.ignore_mandatory = True
- customer.save()
+ customer.save()
if customer_exists:
frappe.rename_doc("Customer", old_name, customer_name)
- address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email})
+ billing_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Billing"})
+ shipping_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Shipping"})
+ rename_address(billing_address, customer)
+ rename_address(shipping_address, customer)
else:
- address = frappe.new_doc("Address")
+ create_address(raw_billing_data, customer, "Billing")
+ create_address(raw_shipping_data, customer, "Shipping")
+ create_contact(raw_billing_data, customer)
- address.address_line1 = raw_billing_data.get("address_1", "Not Provided")
- address.address_line2 = raw_billing_data.get("address_2", "Not Provided")
- address.city = raw_billing_data.get("city", "Not Provided")
- address.woocommerce_email = customer_woo_com_email
- address.address_type = "Billing"
- address.country = frappe.get_value("Country", {"code": raw_billing_data.get("country", "IN").lower()})
- address.state = raw_billing_data.get("state")
- address.pincode = raw_billing_data.get("postcode")
- address.phone = raw_billing_data.get("phone")
- address.email_id = customer_woo_com_email
+def create_contact(data, customer):
+ email = data.get("email", None)
+ phone = data.get("phone", None)
+
+ if not email and not phone:
+ return
+
+ contact = frappe.new_doc("Contact")
+ contact.first_name = data.get("first_name")
+ contact.last_name = data.get("last_name")
+ contact.is_primary_contact = 1
+ contact.is_billing_contact = 1
+
+ if phone:
+ contact.add_phone(phone, is_primary_mobile_no=1, is_primary_phone=1)
+
+ if email:
+ contact.add_email(email, is_primary=1)
+
+ contact.append("links", {
+ "link_doctype": "Customer",
+ "link_name": customer.name
+ })
+
+ contact.flags.ignore_mandatory = True
+ contact.save()
+
+def create_address(raw_data, customer, address_type):
+ address = frappe.new_doc("Address")
+
+ address.address_line1 = raw_data.get("address_1", "Not Provided")
+ address.address_line2 = raw_data.get("address_2", "Not Provided")
+ address.city = raw_data.get("city", "Not Provided")
+ address.woocommerce_email = customer.woocommerce_email
+ address.address_type = address_type
+ address.country = frappe.get_value("Country", {"code": raw_data.get("country", "IN").lower()})
+ address.state = raw_data.get("state")
+ address.pincode = raw_data.get("postcode")
+ address.phone = raw_data.get("phone")
+ address.email_id = customer.woocommerce_email
address.append("links", {
"link_doctype": "Customer",
- "link_name": customer.customer_name
+ "link_name": customer.name
})
+
address.flags.ignore_mandatory = True
- address = address.save()
+ address.save()
- if customer_exists:
- old_address_title = address.name
- new_address_title = customer.customer_name + "-billing"
- address.address_title = customer.customer_name
- address.save()
+def rename_address(address, customer):
+ old_address_title = address.name
+ new_address_title = customer.name + "-" + address.address_type
+ address.address_title = customer.customer_name
+ address.save()
- frappe.rename_doc("Address", old_address_title, new_address_title)
+ frappe.rename_doc("Address", old_address_title, new_address_title)
def link_items(items_list, woocommerce_settings, sys_lang):
for item_data in items_list:
@@ -111,7 +148,7 @@
else:
#Create Item
item = frappe.new_doc("Item")
-
+
item.item_name = item_data.get("name")
item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id"))
item.woocommerce_id = item_data.get("product_id")
@@ -171,7 +208,7 @@
add_tax_details(new_sales_order, order.get("shipping_tax"), "Shipping Tax", woocommerce_settings.f_n_f_account)
add_tax_details(new_sales_order, order.get("shipping_total"), "Shipping Total", woocommerce_settings.f_n_f_account)
-
+
def add_tax_details(sales_order, price, desc, tax_account_head):
sales_order.append("taxes", {
"charge_type":"Actual",
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index b4a5bd1..a706223 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -209,7 +209,7 @@
result.append(new_transaction.name)
except Exception:
- frappe.throw(frappe.get_traceback())
+ frappe.throw(title=_('Bank transaction creation error'))
return result
diff --git a/erpnext/healthcare/dashboard_chart_source/__init__.py b/erpnext/healthcare/dashboard_chart_source/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/__init__.py
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js
new file mode 100644
index 0000000..dd6dc66
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js
@@ -0,0 +1,14 @@
+frappe.provide('frappe.dashboards.chart_sources');
+
+frappe.dashboards.chart_sources["Department wise Patient Appointments"] = {
+ method: "erpnext.healthcare.dashboard_chart_source.department_wise_patient_appointments.department_wise_patient_appointments.get",
+ filters: [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company")
+ }
+ ]
+};
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json
new file mode 100644
index 0000000..00301ef
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json
@@ -0,0 +1,13 @@
+{
+ "creation": "2020-05-18 19:18:42.571045",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart Source",
+ "idx": 0,
+ "modified": "2020-05-18 19:18:42.571045",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Department wise Patient Appointments",
+ "owner": "Administrator",
+ "source_name": "Department wise Patient Appointments",
+ "timeseries": 0
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py
new file mode 100644
index 0000000..062da6e
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils.dashboard import cache_source
+
+@frappe.whitelist()
+@cache_source
+def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
+ to_date = None, timespan = None, time_interval = None, heatmap_year = None):
+ if chart_name:
+ chart = frappe.get_doc('Dashboard Chart', chart_name)
+ else:
+ chart = frappe._dict(frappe.parse_json(chart))
+
+ filters = frappe.parse_json(filters)
+
+ data = frappe.db.get_list('Medical Department', fields=['name'])
+ if not filters:
+ filters = {}
+
+ status = ['Open', 'Scheduled', 'Closed', 'Cancelled']
+ for department in data:
+ filters['department'] = department.name
+ department['total_appointments'] = frappe.db.count('Patient Appointment', filters=filters)
+
+ for entry in status:
+ filters['status'] = entry
+ department[frappe.scrub(entry)] = frappe.db.count('Patient Appointment', filters=filters)
+ filters.pop('status')
+
+ sorted_department_map = sorted(data, key = lambda i: i['total_appointments'], reverse=True)
+
+ if len(sorted_department_map) > 10:
+ sorted_department_map = sorted_department_map[:10]
+
+ labels = []
+ open_appointments = []
+ scheduled = []
+ closed = []
+ cancelled = []
+
+ for department in sorted_department_map:
+ labels.append(department.name)
+ open_appointments.append(department.open)
+ scheduled.append(department.scheduled)
+ closed.append(department.closed)
+ cancelled.append(department.cancelled)
+
+ return {
+ 'labels': labels,
+ 'datasets': [
+ {
+ 'name': 'Open',
+ 'values': open_appointments
+ },
+ {
+ 'name': 'Scheduled',
+ 'values': scheduled
+ },
+ {
+ 'name': 'Closed',
+ 'values': closed
+ },
+ {
+ 'name': 'Cancelled',
+ 'values': cancelled
+ }
+ ],
+ 'type': 'bar'
+ }
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_fixtures.py b/erpnext/healthcare/dashboard_fixtures.py
index fc3d62f..59da71a 100644
--- a/erpnext/healthcare/dashboard_fixtures.py
+++ b/erpnext/healthcare/dashboard_fixtures.py
@@ -3,33 +3,60 @@
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.name
+ return None
+
def get_dashboards():
return [{
"name": "Healthcare",
"dashboard_name": "Healthcare",
"charts": [
- { "chart": "Patient Appointments" }
+ { "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",
+ "chart_name": _("Patient Appointments"),
"timespan": "Last Month",
- "color": "#77ecca",
- "filters_json": json.dumps({}),
+ "filters_json": json.dumps([
+ ["Patient Appointment", "company", "=", company, False],
+ ["Patient Appointment", "status", "!=", "Cancelled"]
+ ]),
"chart_type": "Count",
"timeseries": 1,
"based_on": "appointment_datetime",
@@ -37,5 +64,182 @@
"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 5cf09b3..60b5313 100644
--- a/erpnext/healthcare/desk_page/healthcare/healthcare.json
+++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json
@@ -60,26 +60,30 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Healthcare",
- "modified": "2020-04-25 22:31:36.576444",
+ "modified": "2020-05-19 20:57:22.797267",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare",
+ "onboarding": "Healthcare",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"restrict_to_domain": "Healthcare",
"shortcuts": [
{
+ "color": "#ffe8cd",
"format": "{} Open",
"label": "Patient Appointment",
"link_to": "Patient Appointment",
- "stats_filter": "{\n \"status\": \"Open\"\n}",
+ "stats_filter": "{\n \"status\": \"Open\",\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%']\n}",
"type": "DocType"
},
{
+ "color": "#ffe8cd",
"format": "{} Active",
"label": "Patient",
"link_to": "Patient",
@@ -87,10 +91,11 @@
"type": "DocType"
},
{
+ "color": "#cef6d1",
"format": "{} Vacant",
"label": "Healthcare Service Unit",
"link_to": "Healthcare Service Unit",
- "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0\n}",
+ "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0,\n \"company\": [\"like\", \"%\" + frappe.defaults.get_global_default(\"company\") + \"%\"]\n}",
"type": "DocType"
},
{
@@ -102,6 +107,11 @@
"label": "Patient History",
"link_to": "patient_history",
"type": "Page"
+ },
+ {
+ "label": "Healthcare Dashboard",
+ "link_to": "Healthcare",
+ "type": "Dashboard"
}
]
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
index 87c22cc..eb7d4bd 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
@@ -43,7 +43,8 @@
return {
filters: {
'is_group': false,
- 'allow_appointments': true
+ 'allow_appointments': true,
+ 'company': frm.doc.company
}
};
});
@@ -158,11 +159,13 @@
age = __('{0} as on {1}', [age, data.message.age_as_on]);
}
}
+ frm.set_value('patient_name', data.message.patient_name);
frm.set_value('patient_age', age);
frm.set_value('patient_sex', data.message.sex);
}
});
} else {
+ frm.set_value('patient_name', '');
frm.set_value('patient_age', '');
frm.set_value('patient_sex', '');
}
@@ -177,15 +180,35 @@
name: frm.doc.appointment
},
callback: function(data) {
- frm.set_value('patient', data.message.patient);
- frm.set_value('procedure_template', data.message.procedure_template);
- frm.set_value('medical_department', data.message.department);
- frm.set_value('start_date', data.message.appointment_date);
- frm.set_value('start_time', data.message.appointment_time);
- frm.set_value('notes', data.message.notes);
- frm.set_value('service_unit', data.message.service_unit);
+ let values = {
+ 'patient':data.message.patient,
+ 'procedure_template': data.message.procedure_template,
+ 'medical_department': data.message.department,
+ 'practitioner': data.message.practitioner,
+ 'start_date': data.message.appointment_date,
+ 'start_time': data.message.appointment_time,
+ 'notes': data.message.notes,
+ 'service_unit': data.message.service_unit,
+ 'company': data.message.company
+ };
+ frm.set_value(values);
}
});
+ } else {
+ let values = {
+ 'patient': '',
+ 'patient_name': '',
+ 'patient_sex': '',
+ 'patient_age': '',
+ 'medical_department': '',
+ 'procedure_template': '',
+ 'start_date': '',
+ 'start_time': '',
+ 'notes': '',
+ 'service_unit': '',
+ 'inpatient_record': ''
+ };
+ frm.set_value(values);
}
},
@@ -234,9 +257,11 @@
name: frm.doc.practitioner
},
callback: function (data) {
- frappe.model.set_value(frm.doctype,frm.docname, 'medical_department',data.message.department);
+ frappe.model.set_value(frm.doctype,frm.docname, 'practitioner_name', data.message.practitioner_name);
}
});
+ } else {
+ frappe.model.set_value(frm.doctype,frm.docname, 'practitioner_name', '');
}
},
@@ -284,14 +309,6 @@
});
-cur_frm.set_query('procedure_template', function(doc) {
- return {
- filters: {
- 'medical_department': doc.medical_department
- }
- };
-});
-
frappe.ui.form.on('Clinical Procedure Item', {
qty: function(frm, cdt, cdn) {
let d = locals[cdt][cdn];
diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json
index 3c936bb..eaf8d80 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json
@@ -7,28 +7,32 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "inpatient_record",
"naming_series",
- "procedure_template",
+ "title",
"appointment",
+ "procedure_template",
+ "column_break_30",
+ "company",
+ "invoiced",
+ "section_break_6",
"patient",
+ "patient_name",
"patient_sex",
"patient_age",
- "prescription",
- "medical_department",
- "practitioner",
+ "inpatient_record",
+ "notes",
"column_break_7",
"status",
+ "practitioner",
+ "practitioner_name",
+ "medical_department",
"service_unit",
- "warehouse",
"start_date",
"start_time",
"sample",
- "invoiced",
- "notes",
- "company",
"consumables_section",
"consume_stock",
+ "warehouse",
"items",
"section_break_24",
"invoice_separately_as_consumables",
@@ -36,6 +40,9 @@
"consumable_total_amount",
"column_break_27",
"consumption_details",
+ "sb_refs",
+ "column_break_34",
+ "prescription",
"amended_from"
],
"fields": [
@@ -56,15 +63,15 @@
{
"fieldname": "appointment",
"fieldtype": "Link",
- "in_list_view": 1,
+ "in_standard_filter": 1,
"label": "Appointment",
- "options": "Patient Appointment"
+ "options": "Patient Appointment",
+ "set_only_once": 1
},
{
- "fetch_from": "inpatient_record.patient",
"fieldname": "patient",
"fieldtype": "Link",
- "in_list_view": 1,
+ "in_standard_filter": 1,
"label": "Patient",
"options": "Patient",
"reqd": 1
@@ -88,17 +95,20 @@
"fieldtype": "Link",
"hidden": 1,
"label": "Procedure Prescription",
- "options": "Procedure Prescription"
+ "options": "Procedure Prescription",
+ "read_only": 1
},
{
"fieldname": "medical_department",
"fieldtype": "Link",
+ "in_standard_filter": 1,
"label": "Medical Department",
"options": "Medical Department"
},
{
"fieldname": "practitioner",
"fieldtype": "Link",
+ "in_standard_filter": 1,
"label": "Healthcare Practitioner",
"options": "Healthcare Practitioner"
},
@@ -208,6 +218,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:!doc.__islocal",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
@@ -226,6 +237,8 @@
"read_only": 1
},
{
+ "collapsible": 1,
+ "collapsible_depends_on": "consume_stock",
"fieldname": "consumables_section",
"fieldtype": "Section Break",
"label": "Consumables"
@@ -237,11 +250,51 @@
{
"fieldname": "section_break_24",
"fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_30",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "sb_refs",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "patient_name",
+ "fieldtype": "Data",
+ "label": "Patient Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "practitioner_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Practitioner Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_34",
+ "fieldtype": "Column Break"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Title",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-03-02 11:44:27.970651",
+ "modified": "2020-04-27 21:36:23.796924",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Clinical Procedure",
@@ -257,11 +310,27 @@
"report": 1,
"role": "Nursing User",
"share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Physician",
+ "share": 1,
+ "submit": 1,
"write": 1
}
],
"restrict_to_domain": "Healthcare",
"sort_field": "modified",
"sort_order": "DESC",
+ "title_field": "title",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
index b7d7a62..e55a143 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
@@ -16,6 +16,7 @@
class ClinicalProcedure(Document):
def validate(self):
self.set_status()
+ self.set_title()
if self.consume_stock:
self.set_actual_qty()
@@ -37,7 +38,7 @@
template = frappe.get_doc('Clinical Procedure Template', self.procedure_template)
if template.sample:
patient = frappe.get_doc('Patient', self.patient)
- sample_collection = create_sample_doc(template, patient, None)
+ sample_collection = create_sample_doc(template, patient, None, self.company)
frappe.db.set_value('Clinical Procedure', self.name, 'sample', sample_collection.name)
self.reload()
@@ -50,6 +51,9 @@
elif self.docstatus == 2:
self.status = 'Cancelled'
+ def set_title(self):
+ self.title = _('{0} - {1}').format(self.patient_name or self.patient, self.procedure_template)[:100]
+
def complete_procedure(self):
if self.consume_stock and self.items:
stock_entry = make_stock_entry(self)
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 57f4cdf..16d4540 100644
--- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
+++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
@@ -144,3 +144,38 @@
}
};
});
+
+frappe.tour['Clinical Procedure Template'] = [
+ {
+ fieldname: 'template',
+ title: __('Template Name'),
+ description: __('Enter a name for the Clinical Procedure Template')
+ },
+ {
+ fieldname: 'item_code',
+ title: __('Item Code'),
+ description: __('Set the Item Code which will be used for billing the Clinical Procedure.')
+ },
+ {
+ fieldname: 'item_group',
+ title: __('Item Group'),
+ description: __('Select an Item Group for the Clinical Procedure Item.')
+ },
+ {
+ fieldname: 'is_billable',
+ title: __('Clinical Procedure Rate'),
+ description: __('Check this if the Clinical Procedure is billable and also set the rate.')
+ },
+ {
+ fieldname: 'consume_stock',
+ title: __('Allow Stock Consumption'),
+ description: __('Check this if the Clinical Procedure utilises consumables. Click ') + "<a href='https://docs.erpnext.com/docs/user/manual/en/healthcare/clinical_procedure_template#22-manage-procedure-consumables' target='_blank'>here</a>" + __(' to know more')
+
+ },
+ {
+ fieldname: 'medical_department',
+ title: __('Medical Department'),
+ description: __('You can also set the Medical Department for the template. After saving the document, an Item will automatically be created for billing this Clinical Procedure. You can then use this template while creating Clinical Procedures for Patients. Templates save you from filling up redundant data every single time. You can also create templates for other operations like Lab Tests, Therapy Sessions, etc.')
+ }
+];
+
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
index 4ab3b6e..fc0b241 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
@@ -108,3 +108,38 @@
});
}
});
+
+frappe.tour['Healthcare Practitioner'] = [
+ {
+ fieldname: 'employee',
+ title: __('Employee'),
+ description: __('If you want to track Payroll and other HRMS operations for a Practitoner, create an Employee and link it here.')
+ },
+ {
+ fieldname: 'practitioner_schedules',
+ title: __('Practitioner Schedules'),
+ description: __('Set the Practitioner Schedule you just created. This will be used while booking appointments.')
+ },
+ {
+ fieldname: 'op_consulting_charge_item',
+ title: __('Out Patient Consulting Charge Item'),
+ description: __('Create a service item for Out Patient Consulting.')
+ },
+ {
+ fieldname: 'inpatient_visit_charge_item',
+ title: __('Inpatient Visit Charge Item'),
+ description: __('If this Healthcare Practitioner works for the In-Patient Department, create a service item for Inpatient Visits.')
+ },
+ {
+ fieldname: 'op_consulting_charge',
+ title: __('Out Patient Consulting Charge'),
+ description: __('Set the Out Patient Consulting Charge for this Practitioner.')
+
+ },
+ {
+ fieldname: 'inpatient_visit_charge',
+ title: __('Inpatient Visit Charge'),
+ description: __('If this Healthcare Practitioner also works for the In-Patient Department, set the inpatient visit charge for this Practitioner.')
+ }
+];
+
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
index fd5b6e1..cb747f9 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
@@ -1,6 +1,5 @@
{
"actions": [],
- "allow_copy": 1,
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
@@ -51,17 +50,20 @@
"fieldname": "first_name",
"fieldtype": "Data",
"label": "First Name",
+ "no_copy": 1,
"reqd": 1
},
{
"fieldname": "middle_name",
"fieldtype": "Data",
- "label": "Middle Name (Optional)"
+ "label": "Middle Name (Optional)",
+ "no_copy": 1
},
{
"fieldname": "last_name",
"fieldtype": "Data",
- "label": "Last Name"
+ "label": "Last Name",
+ "no_copy": 1
},
{
"fieldname": "image",
@@ -226,6 +228,7 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Full Name",
+ "no_copy": 1,
"read_only": 1,
"search_index": 1
},
@@ -233,6 +236,7 @@
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
+ "no_copy": 1,
"options": "HLC-PRAC-.YYYY.-",
"report_hide": 1,
"set_only_once": 1
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
index 22fbf50..c266ba8 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
@@ -39,3 +39,37 @@
};
});
};
+
+frappe.tour['Healthcare Settings'] = [
+ {
+ fieldname: 'link_customer_to_patient',
+ title: __('Link Customer to Patient'),
+ description: __('If checked, a customer will be created for every Patient. Patient Invoices will be created against this Customer. You can also select existing Customer while creating a Patient. This field is checked by default.')
+ },
+ {
+ fieldname: 'collect_registration_fee',
+ title: __('Collect Registration Fee'),
+ description: __('If your Healthcare facility bills registrations of Patients, you can check this and set the Registration Fee in the field below. Checking this will create new Patients with a Disabled status by default and will only be enabled after invoicing the Registration Fee.')
+ },
+ {
+ fieldname: 'automate_appointment_invoicing',
+ title: __('Automate Appointment Invoicing'),
+ description: __('Checking this will automatically create a Sales Invoice whenever an appointment is booked for a Patient.')
+ },
+ {
+ fieldname: 'healthcare_service_items',
+ title: __('Healthcare Service Items'),
+ description: __('Set up the Healthcare Service Items for billing. Click ') + "<a href='https://docs.erpnext.com/docs/user/manual/en/healthcare/healthcare_settings#2-default-healthcare-service-items' target='_blank'>here</a>" + __(' to know more')
+ },
+ {
+ fieldname: 'sb_in_ac',
+ title: __('Set up default Accounts for the Healthcare Facility'),
+ description: __('If you wish to override default accounts settings and configure the Income and Receivable accounts for Healthcare, you can do so here.')
+
+ },
+ {
+ fieldname: 'out_patient_sms_alerts',
+ title: __('Out Patient SMS alerts'),
+ description: __('You can set up Out Patient SMS alerts here. Click ') + "<a href='https://docs.erpnext.com/docs/user/manual/en/healthcare/healthcare_settings#4-out-patient-sms-alerts' target='_blank'>here</a>" + __(' to know more')
+ }
+];
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
index de08620..2f0115c 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
@@ -240,7 +240,7 @@
"label": "Patient Registration"
},
{
- "default": "Hello {{doc.patient}}, Thank you for registering with {{doc.company}}. Your ID is {{doc.id}} . Please note this ID for future reference. \nThank You, Get well soon!",
+ "default": "Hello {{doc.patient}}, Thank you for registering with {{doc.company}}. Your ID is {{doc.name}} . Please note this ID for future reference. \nThank You!",
"depends_on": "send_registration_msg",
"fieldname": "registration_msg",
"fieldtype": "Small Text",
@@ -254,7 +254,7 @@
"label": "Appointment Confirmation"
},
{
- "default": "Hello {{doc.patient}}, You have scheduled an appointment with {{doc.practitioner}} by {{doc.start_dt}} at {{doc.company}}.\nThank you, Good day!",
+ "default": "Hello {{doc.patient}}, You have scheduled an appointment with {{doc.practitioner}} on {{doc.appointment_datetime}} at {{doc.company}}.\nThank you, Good day!",
"depends_on": "send_appointment_confirmation",
"fieldname": "appointment_confirmation_msg",
"fieldtype": "Small Text",
@@ -276,7 +276,7 @@
"label": "Appointment Reminder"
},
{
- "default": "Hello {{doc.patient}}, You have an appointment with {{doc.practitioner}} by {{doc.appointment_time}} at {{doc.company}}.\nThank you, Good day!\n",
+ "default": "Hello {{doc.patient}}, You have an appointment with {{doc.practitioner}} by {{doc.appointment_datetime}} at {{doc.company}}.\nThank you, Good day!\n",
"depends_on": "send_appointment_reminder",
"fieldname": "appointment_reminder_msg",
"fieldtype": "Small Text",
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
index 92c11fb..c1b516d 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
@@ -1,980 +1,255 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2018-07-11 17:48:51.404139",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "naming_series:",
+ "creation": "2018-07-11 17:48:51.404139",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "section_break_1",
+ "naming_series",
+ "patient",
+ "patient_name",
+ "gender",
+ "blood_group",
+ "dob",
+ "mobile",
+ "email",
+ "phone",
+ "column_break_8",
+ "company",
+ "status",
+ "scheduled_date",
+ "admitted_datetime",
+ "expected_discharge",
+ "discharge_date",
+ "references",
+ "cb_admission",
+ "admission_practitioner",
+ "admission_encounter",
+ "cb_discharge",
+ "discharge_practitioner",
+ "discharge_encounter",
+ "sb_inpatient_occupancy",
+ "inpatient_occupancies",
+ "btn_transfer",
+ "sb_discharge_note",
+ "discharge_note"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_1",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_1",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "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": "Series",
- "length": 0,
- "no_copy": 0,
- "options": "HLC-INP-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Series",
+ "options": "HLC-INP-.YYYY.-"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "patient",
- "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": "Patient",
- "length": 0,
- "no_copy": 0,
- "options": "Patient",
- "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": "patient",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Patient",
+ "options": "Patient",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "patient.patient_name",
- "fieldname": "patient_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Patient 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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "patient.patient_name",
+ "fieldname": "patient_name",
+ "fieldtype": "Data",
+ "label": "Patient Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "patient.sex",
- "fieldname": "gender",
- "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": "Gender",
- "length": 0,
- "no_copy": 0,
- "options": "Gender",
- "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": "patient.sex",
+ "fieldname": "gender",
+ "fieldtype": "Link",
+ "label": "Gender",
+ "options": "Gender",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "patient.blood_group",
- "fieldname": "blood_group",
- "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": "Blood Group",
- "length": 0,
- "no_copy": 0,
- "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative",
- "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": "patient.blood_group",
+ "fieldname": "blood_group",
+ "fieldtype": "Select",
+ "label": "Blood Group",
+ "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "dob",
- "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 of birth",
- "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": "patient.dob",
+ "fieldname": "dob",
+ "fieldtype": "Date",
+ "label": "Date of birth",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "patient.mobile",
- "fieldname": "mobile",
- "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": "Mobile",
- "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": "patient.mobile",
+ "fieldname": "mobile",
+ "fieldtype": "Data",
+ "label": "Mobile",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "patient.email",
- "fieldname": "email",
- "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": "Email",
- "length": 0,
- "no_copy": 0,
- "options": "Email",
- "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": "patient.email",
+ "fieldname": "email",
+ "fieldtype": "Data",
+ "label": "Email",
+ "options": "Email",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "patient.phone",
- "fieldname": "phone",
- "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": "Phone",
- "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": "patient.phone",
+ "fieldname": "phone",
+ "fieldtype": "Data",
+ "label": "Phone",
+ "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_8",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "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": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 0,
- "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged",
- "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": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Status",
+ "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fieldname": "scheduled_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": "Admission 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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Today",
+ "fieldname": "scheduled_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Admission Schedule Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fieldname": "admitted_datetime",
- "fieldtype": "Datetime",
- "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": "Admitted Datetime",
- "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": "Today",
+ "fieldname": "admitted_datetime",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "Admitted Datetime"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "expected_discharge",
- "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": "Expected Discharge",
- "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": "expected_discharge",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Expected Discharge"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "discharge_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": "Discharge 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": "discharge_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Discharge Date"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "columns": 0,
- "fieldname": "references",
- "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": "References",
- "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
- },
+ "collapsible": 1,
+ "fieldname": "references",
+ "fieldtype": "Section Break",
+ "label": "References"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "cb_admission",
- "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": "Admission",
- "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": "cb_admission",
+ "fieldtype": "Column Break",
+ "label": "Admission"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "admission_practitioner",
- "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": "Healthcare Practitioner",
- "length": 0,
- "no_copy": 0,
- "options": "Healthcare Practitioner",
- "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": "admission_practitioner",
+ "fieldtype": "Link",
+ "label": "Healthcare Practitioner",
+ "options": "Healthcare Practitioner",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "admission_encounter",
- "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": "Patient Encounter",
- "length": 0,
- "no_copy": 0,
- "options": "Patient Encounter",
- "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": "admission_encounter",
+ "fieldtype": "Link",
+ "label": "Patient Encounter",
+ "options": "Patient Encounter",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "cb_discharge",
- "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": "Discharge",
- "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": "cb_discharge",
+ "fieldtype": "Column Break",
+ "label": "Discharge"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "discharge_practitioner",
- "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": "Healthcare Practitioner",
- "length": 0,
- "no_copy": 0,
- "options": "Healthcare Practitioner",
- "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": "discharge_practitioner",
+ "fieldtype": "Link",
+ "label": "Healthcare Practitioner",
+ "options": "Healthcare Practitioner",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "discharge_encounter",
- "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": "Patient Encounter",
- "length": 0,
- "no_copy": 0,
- "options": "Patient Encounter",
- "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": "discharge_encounter",
+ "fieldtype": "Link",
+ "label": "Patient Encounter",
+ "options": "Patient Encounter",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sb_inpatient_occupancy",
- "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": "Inpatient Occupancy",
- "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": "sb_inpatient_occupancy",
+ "fieldtype": "Section Break",
+ "label": "Inpatient Occupancy"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "inpatient_occupancies",
- "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,
- "length": 0,
- "no_copy": 0,
- "options": "Inpatient Occupancy",
- "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": "inpatient_occupancies",
+ "fieldtype": "Table",
+ "options": "Inpatient Occupancy",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "btn_transfer",
- "fieldtype": "Button",
- "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": "Transfer",
- "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": "btn_transfer",
+ "fieldtype": "Button",
+ "label": "Transfer"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.status != \"Admission Scheduled\"",
- "fieldname": "sb_discharge_note",
- "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": "Discharge Note",
- "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
- },
+ "depends_on": "eval:doc.status != \"Admission Scheduled\"",
+ "fieldname": "sb_discharge_note",
+ "fieldtype": "Section Break",
+ "label": "Discharge Note"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "discharge_note",
- "fieldtype": "Text Editor",
- "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": "discharge_note",
+ "fieldtype": "Text Editor"
+ },
+ {
+ "fetch_from": "admission_encounter.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Company",
+ "options": "Company"
}
- ],
- "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-08-21 14:44:43.168245",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Inpatient Record",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "links": [],
+ "modified": "2020-04-07 13:13:39.351977",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Inpatient Record",
+ "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": "Healthcare Administrator",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Healthcare Administrator",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Healthcare",
- "search_fields": "patient",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "patient",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
-}
+ ],
+ "restrict_to_domain": "Healthcare",
+ "search_fields": "patient",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "patient",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
index e15324c..4c2d3f6 100644
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
@@ -8,7 +8,6 @@
from frappe.utils import now_datetime, today
from frappe.utils.make_random import get_random
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
-from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient
class TestInpatientRecord(unittest.TestCase):
def test_admit_and_discharge(self):
@@ -112,3 +111,13 @@
service_unit_type.save(ignore_permissions = True)
return service_unit_type.name
return service_unit_type
+
+def create_patient():
+ patient = frappe.db.exists('Patient', '_Test IPD Patient')
+ if not patient:
+ patient = frappe.new_doc('Patient')
+ patient.first_name = '_Test IPD Patient'
+ patient.sex = 'Female'
+ patient.save(ignore_permissions=True)
+ patient = patient.name
+ return patient
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js
index 5b3f4c7..bf1ecc8 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.js
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.js
@@ -137,13 +137,13 @@
});
}
else{
- frappe.msgprint(__("Please select Patient to get Lab Tests"));
+ frappe.msgprint(__("Please select a Patient to get Lab Tests"));
}
};
var show_lab_tests = function(frm, result){
var d = new frappe.ui.Dialog({
- title: __("Lab Test Prescriptions"),
+ title: __("Lab Tests"),
fields: [
{
fieldtype: "HTML", fieldname: "lab_test"
@@ -161,7 +161,7 @@
<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 Test\
+ 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");
@@ -180,9 +180,10 @@
return false;
});
});
- if(!result){
- var msg = "There are no Lab Test prescribed for "+frm.doc.patient;
- $(repl('<div class="col-xs-12" style="padding-top:20px;" >%(msg)s</div></div>', {msg: msg})).appendTo(html_field);
+ if(!result.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);
}
d.show();
};
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json
index ccbc24b..17dc1ed 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.json
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.json
@@ -9,18 +9,18 @@
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
- "inpatient_record",
"naming_series",
- "invoiced",
"patient",
"patient_name",
"patient_age",
"patient_sex",
- "practitioner",
+ "report_preference",
"email",
"mobile",
- "company",
+ "practitioner",
"c_b",
+ "inpatient_record",
+ "company",
"department",
"status",
"submitted_date",
@@ -31,7 +31,7 @@
"employee_name",
"employee_designation",
"user",
- "report_preference",
+ "invoiced",
"sb_first",
"lab_test_name",
"column_break_26",
@@ -153,7 +153,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
- "hidden": 1,
+ "in_standard_filter": 1,
"label": "Company",
"options": "Company",
"print_hide": 1,
@@ -168,6 +168,7 @@
"fieldname": "department",
"fieldtype": "Link",
"ignore_user_permissions": 1,
+ "in_standard_filter": 1,
"label": "Department",
"options": "Medical Department",
"search_index": 1
@@ -427,7 +428,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-03-23 19:37:06.617764",
+ "modified": "2020-04-04 19:16:29.131168",
"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 ea8ce25..b2c5e6b 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.py
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.py
@@ -69,9 +69,9 @@
lab_test_created = create_lab_test_from_encounter(docname)
if lab_test_created:
- frappe.msgprint(_("Lab Test(s) "+lab_test_created+" created."))
+ frappe.msgprint(_("Lab Test(s) {0} created".format(lab_test_created)))
else:
- frappe.msgprint(_("No Lab Test created"))
+ frappe.msgprint(_("No Lab Tests created"))
def create_lab_test_from_encounter(encounter_id):
lab_test_created = False
@@ -87,7 +87,7 @@
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)
+ 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:
@@ -111,7 +111,7 @@
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)
+ lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template, invoice.company)
if item.reference_dt == "Lab Prescription":
lab_test.prescription = item.reference_dn
lab_test.save(ignore_permissions = True)
@@ -121,7 +121,7 @@
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):
@@ -141,7 +141,7 @@
return template_exists
return False
-def create_lab_test_doc(invoiced, practitioner, patient, template):
+def create_lab_test_doc(invoiced, practitioner, patient, template, company):
lab_test = frappe.new_doc("Lab Test")
lab_test.invoiced = invoiced
lab_test.practitioner = practitioner
@@ -150,11 +150,12 @@
lab_test.patient_sex = patient.sex
lab_test.email = patient.email
lab_test.mobile = patient.mobile
+ lab_test.report_preference = patient.report_preference
lab_test.department = template.department
lab_test.template = template.name
lab_test.lab_test_group = template.lab_test_group
lab_test.result_date = getdate()
- lab_test.report_preference = patient.report_preference
+ lab_test.company = company
return lab_test
def create_normals(template, lab_test):
@@ -190,7 +191,7 @@
special.require_result_value = 1
special.template = template.name
-def create_sample_doc(template, patient, invoice):
+def create_sample_doc(template, patient, invoice, company = None):
if template.sample:
sample_exists = frappe.db.exists({
"doctype": "Sample Collection",
@@ -221,6 +222,8 @@
sample_collection.sample = template.sample
sample_collection.sample_uom = template.sample_uom
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
sample_collection.save(ignore_permissions=True)
@@ -229,7 +232,7 @@
def create_sample_collection(lab_test, template, patient, invoice):
if(frappe.db.get_value("Healthcare Settings", None, "create_sample_collection_for_lab_test") == "1"):
- sample_collection = create_sample_doc(template, patient, invoice)
+ sample_collection = create_sample_doc(template, patient, invoice, lab_test.company)
if(sample_collection):
lab_test.sample = sample_collection.name
return lab_test
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 e2b47b4..3521561 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
@@ -115,7 +115,7 @@
"price_list": price_list_name,
"item_code": item,
"price_list_rate": item_price
- }).insert(ignore_permissions=True)
+ }).insert(ignore_permissions=True, ignore_mandatory=True)
@frappe.whitelist()
def change_test_code_from_template(lab_test_code, doc):
diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js
index d5df956..490f247 100644
--- a/erpnext/healthcare/doctype/patient/patient.js
+++ b/erpnext/healthcare/doctype/patient/patient.js
@@ -10,6 +10,8 @@
]
};
});
+ frm.set_query('customer_group', {'is_group': 0});
+ frm.set_query('default_price_list', { 'selling': 1});
if (frappe.defaults.get_default('patient_name_by') != 'Naming Series') {
frm.toggle_display('naming_series', false);
@@ -40,6 +42,7 @@
frm.add_custom_button(__('Patient Encounter'), function () {
create_encounter(frm);
}, 'Create');
+ frm.toggle_enable(['customer'], 0); // ToDo, allow change only if no transactions booked or better, add merge option
}
},
onload: function (frm) {
diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json
index 4258e40..8af1a9c 100644
--- a/erpnext/healthcare/doctype/patient/patient.json
+++ b/erpnext/healthcare/doctype/patient/patient.json
@@ -24,13 +24,20 @@
"image",
"column_break_14",
"status",
- "inpatient_status",
"inpatient_record",
- "customer",
+ "inpatient_status",
+ "report_preference",
"mobile",
"email",
"phone",
- "report_preference",
+ "customer_details_section",
+ "customer",
+ "customer_group",
+ "territory",
+ "column_break_24",
+ "default_currency",
+ "default_price_list",
+ "language",
"personal_and_social_history",
"occupation",
"column_break_25",
@@ -52,9 +59,7 @@
"surrounding_factors",
"other_risk_factors",
"more_info",
- "patient_details",
- "ac_sb",
- "default_currency"
+ "patient_details"
],
"fields": [
{
@@ -67,6 +72,7 @@
{
"fieldname": "inpatient_status",
"fieldtype": "Select",
+ "in_preview": 1,
"label": "Inpatient Status",
"options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled",
"read_only": 1
@@ -101,6 +107,7 @@
{
"fieldname": "sex",
"fieldtype": "Link",
+ "in_preview": 1,
"label": "Gender",
"options": "Gender",
"reqd": 1
@@ -109,6 +116,7 @@
"bold": 1,
"fieldname": "blood_group",
"fieldtype": "Select",
+ "in_preview": 1,
"label": "Blood Group",
"options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative"
},
@@ -116,6 +124,7 @@
"bold": 1,
"fieldname": "dob",
"fieldtype": "Date",
+ "in_preview": 1,
"label": "Date of birth"
},
{
@@ -142,6 +151,7 @@
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
+ "in_preview": 1,
"label": "Image",
"no_copy": 1,
"print_hide": 1,
@@ -157,7 +167,8 @@
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Customer",
- "options": "Customer"
+ "options": "Customer",
+ "set_only_once": 1
},
{
"fieldname": "report_preference",
@@ -171,7 +182,8 @@
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
- "label": "Mobile"
+ "label": "Mobile",
+ "options": "Phone"
},
{
"bold": 1,
@@ -186,7 +198,8 @@
"fieldname": "phone",
"fieldtype": "Data",
"in_filter": 1,
- "label": "Phone"
+ "label": "Phone",
+ "options": "Phone"
},
{
"collapsible": 1,
@@ -268,25 +281,25 @@
"fieldname": "tobacco_past_use",
"fieldtype": "Data",
"ignore_xss_filter": 1,
- "label": "Tobacco Consumption Habbits (Past)"
+ "label": "Tobacco Consumption (Past)"
},
{
"fieldname": "tobacco_current_use",
"fieldtype": "Data",
"ignore_xss_filter": 1,
- "label": "Tobacco Consumption Habbits (Present)"
+ "label": "Tobacco Consumption (Present)"
},
{
"fieldname": "alcohol_past_use",
"fieldtype": "Data",
"ignore_xss_filter": 1,
- "label": "Alcohol Consumption Habbits (Past)"
+ "label": "Alcohol Consumption (Past)"
},
{
"fieldname": "alcohol_current_use",
"fieldtype": "Data",
"ignore_user_permissions": 1,
- "label": "Alcohol Consumption Habbits (Present)"
+ "label": "Alcohol Consumption (Present)"
},
{
"fieldname": "column_break_32",
@@ -321,19 +334,10 @@
"label": "Patient Details"
},
{
- "collapsible": 1,
- "fieldname": "ac_sb",
- "fieldtype": "Section Break",
- "label": "Account Details"
- },
- {
"fieldname": "default_currency",
"fieldtype": "Link",
- "hidden": 1,
- "ignore_xss_filter": 1,
- "label": "Default Currency",
- "options": "Currency",
- "print_hide": 1
+ "label": "Billing Currency",
+ "options": "Currency"
},
{
"fieldname": "last_name",
@@ -351,13 +355,47 @@
"fieldname": "middle_name",
"fieldtype": "Data",
"label": "Middle Name (optional)"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "customer_details_section",
+ "fieldtype": "Section Break",
+ "label": "Customer Details"
+ },
+ {
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "label": "Customer Group",
+ "options": "Customer Group"
+ },
+ {
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "label": "Territory",
+ "options": "Territory"
+ },
+ {
+ "fieldname": "column_break_24",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "default_price_list",
+ "fieldtype": "Link",
+ "label": "Default Price List",
+ "options": "Price List"
+ },
+ {
+ "fieldname": "language",
+ "fieldtype": "Link",
+ "label": "Print Language",
+ "options": "Language"
}
],
"icon": "fa fa-user",
"image_field": "image",
"links": [],
"max_attachments": 50,
- "modified": "2020-04-06 12:55:30.807744",
+ "modified": "2020-04-25 17:24:32.146415",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient",
diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py
index e304a0b..30a1e45 100644
--- a/erpnext/healthcare/doctype/patient/patient.py
+++ b/erpnext/healthcare/doctype/patient/patient.py
@@ -10,6 +10,7 @@
import dateutil
from frappe.model.naming import set_name_by_naming_series
from frappe.utils.nestedset import get_root_of
+from erpnext import get_default_currency
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account, send_registration_sms
class Patient(Document):
@@ -17,6 +18,9 @@
self.set_full_name()
self.add_as_website_user()
+ def before_insert(self):
+ self.set_missing_customer_details()
+
def after_insert(self):
self.add_as_website_user()
self.reload()
@@ -26,6 +30,25 @@
frappe.db.set_value('Patient', self.name, 'status', 'Disabled')
else:
send_registration_sms(self)
+ self.reload() # self.notify_update()
+
+ def on_update(self):
+ if self.customer:
+ customer = frappe.get_doc('Customer', self.customer)
+ if self.customer_group:
+ customer.customer_group = self.customer_group
+ if self.territory:
+ customer.territory = self.territory
+
+ customer.customer_name = self.patient_name
+ customer.default_price_list = self.default_price_list
+ customer.default_currency = self.default_currency
+ customer.language = self.language
+ customer.ignore_mandatory = True
+ customer.save(ignore_permissions=True)
+ else:
+ if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'):
+ create_customer(self)
def set_full_name(self):
if self.last_name:
@@ -33,6 +56,22 @@
else:
self.patient_name = self.first_name
+ def set_missing_customer_details(self):
+ if not self.customer_group:
+ self.customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group') or get_root_of('Customer Group')
+ if not self.territory:
+ self.territory = frappe.db.get_single_value('Selling Settings', 'territory') or get_root_of('Territory')
+ if not self.default_price_list:
+ self.default_price_list = frappe.db.get_single_value('Selling Settings', 'selling_price_list')
+
+ if not self.customer_group or not self.territory or not self.default_price_list:
+ frappe.msgprint(_('Please set defaults for Customer Group, Territory and Selling Price List in Selling Settings'), alert=True)
+
+ if not self.default_currency:
+ self.default_currency = get_default_currency()
+ if not self.language:
+ self.language = frappe.db.get_single_value('System Settings', 'language')
+
def add_as_website_user(self):
if self.email:
if not frappe.db.exists ('User', self.email):
@@ -86,19 +125,15 @@
return {'invoice': sales_invoice.name}
def create_customer(doc):
- customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group')
- territory = frappe.db.get_single_value('Selling Settings', 'territory')
- if not (customer_group and territory):
- customer_group = get_root_of('Customer Group')
- territory = get_root_of('Territory')
- frappe.msgprint(_('Please set default customer group and territory in Selling Settings'), alert=True)
-
customer = frappe.get_doc({
'doctype': 'Customer',
'customer_name': doc.patient_name,
- 'customer_group': customer_group,
- 'territory' : territory,
- 'customer_type': 'Individual'
+ 'customer_group': doc.customer_group or frappe.db.get_single_value('Selling Settings', 'customer_group'),
+ 'territory' : doc.territory or frappe.db.get_single_value('Selling Settings', 'territory'),
+ 'customer_type': 'Individual',
+ 'default_currency': doc.default_currency,
+ 'default_price_list': doc.default_price_list,
+ 'language': doc.language
}).insert(ignore_permissions=True, ignore_mandatory=True)
frappe.db.set_value('Patient', doc.name, 'customer', customer.name)
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
index fa58934..f7ed31b 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
@@ -32,8 +32,9 @@
frm.set_query('service_unit', function(){
return {
filters: {
- 'is_group': 0,
- 'allow_appointments': 1
+ 'is_group': false,
+ 'allow_appointments': true,
+ 'company': frm.doc.company
}
};
});
@@ -127,6 +128,11 @@
patient: function(frm) {
if (frm.doc.patient) {
frm.trigger('toggle_payment_fields');
+ } else {
+ frm.set_value('patient_name', '');
+ frm.set_value('patient_sex', '');
+ frm.set_value('patient_age', '');
+ frm.set_value('inpatient_record', '');
}
},
@@ -230,7 +236,6 @@
d.hide();
frm.enable_save();
frm.save();
- frm.enable_save();
d.get_primary_btn().attr('disabled', true);
}
});
@@ -481,6 +486,7 @@
frappe.route_options = {
'patient': frm.doc.patient,
'appointment': frm.doc.name,
+ 'company': frm.doc.company
};
frappe.new_doc('Vital Signs');
};
@@ -513,6 +519,7 @@
callback: function (data) {
frappe.model.set_value(frm.doctype, frm.docname, 'department', data.message.department);
frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.op_consulting_charge);
+ frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.op_consulting_charge_item);
}
});
}
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index 57e6c47..b8a400c 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
@@ -10,40 +10,44 @@
"engine": "InnoDB",
"field_order": [
"naming_series",
+ "title",
+ "status",
"patient",
"patient_name",
"patient_sex",
"patient_age",
"inpatient_record",
"column_break_1",
- "status",
+ "company",
+ "service_unit",
"procedure_template",
"get_procedure_from_encounter",
"procedure_prescription",
"therapy_type",
"get_prescribed_therapies",
"therapy_plan",
- "service_unit",
- "section_break_12",
"practitioner",
+ "practitioner_name",
"department",
+ "section_break_12",
"appointment_type",
+ "duration",
"column_break_17",
"appointment_date",
"appointment_time",
"appointment_datetime",
- "duration",
"section_break_16",
"mode_of_payment",
- "paid_amount",
- "company",
+ "billing_item",
"column_break_2",
+ "paid_amount",
"invoiced",
"ref_sales_invoice",
"section_break_3",
- "notes",
"referring_practitioner",
- "reminded"
+ "reminded",
+ "column_break_36",
+ "notes"
],
"fields": [
{
@@ -55,7 +59,6 @@
"read_only": 1
},
{
- "fetch_from": "inpatient_record.patient",
"fieldname": "patient",
"fieldtype": "Link",
"ignore_user_permissions": 1,
@@ -79,7 +82,8 @@
"fieldname": "duration",
"fieldtype": "Int",
"in_filter": 1,
- "label": "Duration (In Minutes)"
+ "label": "Duration (In Minutes)",
+ "set_only_once": 1
},
{
"fieldname": "column_break_1",
@@ -98,6 +102,7 @@
"search_index": 1
},
{
+ "depends_on": "eval:doc.patient;",
"fieldname": "procedure_template",
"fieldtype": "Link",
"label": "Clinical Procedure Template",
@@ -117,7 +122,8 @@
"label": "Procedure Prescription",
"no_copy": 1,
"options": "Procedure Prescription",
- "print_hide": 1
+ "print_hide": 1,
+ "read_only": 1
},
{
"fieldname": "service_unit",
@@ -128,7 +134,8 @@
},
{
"fieldname": "section_break_12",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Appointment Details"
},
{
"fieldname": "practitioner",
@@ -143,6 +150,7 @@
"set_only_once": 1
},
{
+ "fetch_from": "practitioner.department",
"fieldname": "department",
"fieldtype": "Link",
"ignore_user_permissions": 1,
@@ -173,11 +181,13 @@
"fieldtype": "Time",
"in_list_view": 1,
"label": "Time",
- "read_only": 1
+ "read_only": 1,
+ "reqd": 1
},
{
"fieldname": "section_break_16",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Payments"
},
{
"fetch_from": "patient.patient_name",
@@ -206,6 +216,7 @@
{
"fieldname": "appointment_datetime",
"fieldtype": "Datetime",
+ "hidden": 1,
"label": "Appointment Datetime",
"print_hide": 1,
"read_only": 1,
@@ -237,12 +248,12 @@
{
"fieldname": "company",
"fieldtype": "Link",
- "hidden": 1,
+ "in_standard_filter": 1,
"label": "Company",
"no_copy": 1,
"options": "Company",
- "print_hide": 1,
- "report_hide": 1
+ "reqd": 1,
+ "set_only_once": 1
},
{
"collapsible": 1,
@@ -307,10 +318,37 @@
"label": "Series",
"options": "HLC-APP-.YYYY.-",
"set_only_once": 1
+ },
+ {
+ "fieldname": "billing_item",
+ "fieldtype": "Link",
+ "label": "Billing Item",
+ "options": "Item",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_36",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Title",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fetch_from": "practitioner.practitioner_name",
+ "fieldname": "practitioner_name",
+ "fieldtype": "Data",
+ "label": "Practitioner Name",
+ "read_only": 1
}
],
"links": [],
- "modified": "2020-03-31 16:16:32.116865",
+ "modified": "2020-04-27 21:36:06.404062",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Appointment",
@@ -358,7 +396,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
- "title_field": "patient",
+ "title_field": "title",
"track_changes": 1,
"track_seen": 1
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index c4ec30f..9eb6e77 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -21,6 +21,7 @@
self.set_appointment_datetime()
self.validate_customer_created()
self.set_status()
+ self.set_title()
def after_insert(self):
self.update_prescription_details()
@@ -28,6 +29,10 @@
self.update_fee_validity()
send_confirmation_msg(self)
+ def set_title(self):
+ self.title = _('{0} with {1}').format(self.patient_name or self.patient,
+ self.practitioner_name or self.practitioner)
+
def set_status(self):
today = getdate()
appointment_date = getdate(self.appointment_date)
@@ -119,25 +124,28 @@
if automate_invoicing and not appointment_invoiced and not fee_validity:
sales_invoice = frappe.new_doc('Sales Invoice')
+ sales_invoice.patient = appointment_doc.patient
sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer')
sales_invoice.appointment = appointment_doc.name
sales_invoice.due_date = getdate()
- sales_invoice.is_pos = 1
sales_invoice.company = appointment_doc.company
sales_invoice.debit_to = get_receivable_account(appointment_doc.company)
item = sales_invoice.append('items', {})
item = get_appointment_item(appointment_doc, item)
- payment = sales_invoice.append('payments', {})
- payment.mode_of_payment = appointment_doc.mode_of_payment
- payment.amount = appointment_doc.paid_amount
+ # Add payments if payment details are supplied else proceed to create invoice as Unpaid
+ if appointment_doc.mode_of_payment and appointment_doc.paid_amount:
+ sales_invoice.is_pos = 1
+ payment = sales_invoice.append('payments', {})
+ payment.mode_of_payment = appointment_doc.mode_of_payment
+ payment.amount = appointment_doc.paid_amount
sales_invoice.set_missing_values(for_validate=True)
sales_invoice.flags.ignore_mandatory = True
sales_invoice.save(ignore_permissions=True)
sales_invoice.submit()
- frappe.msgprint(_('Sales Invoice {0} created as paid'.format(sales_invoice.name)), alert=True)
+ frappe.msgprint(_('Sales Invoice {0} created'.format(sales_invoice.name)), alert=True)
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'invoiced', 1)
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name)
@@ -343,8 +351,8 @@
['practitioner', 'practitioner'],
['medical_department', 'department'],
['patient_sex', 'patient_sex'],
- ['encounter_date', 'appointment_date'],
- ['invoiced', 'invoiced']
+ ['invoiced', 'invoiced'],
+ ['company', 'company']
]
}
}, target_doc)
@@ -370,17 +378,19 @@
frappe.db.set_value('Patient Appointment', doc.name, 'reminded', 1)
def send_message(doc, message):
- patient = frappe.get_doc('Patient', doc.patient)
- if patient.mobile:
+ patient_mobile = frappe.db.get_value('Patient', doc.patient, 'mobile')
+ if patient_mobile:
context = {'doc': doc, 'alert': doc, 'comments': None}
if doc.get('_comments'):
context['comments'] = json.loads(doc.get('_comments'))
# jinja to string convertion happens here
message = frappe.render_template(message, context)
- number = [patient.mobile]
- send_sms(number, message)
-
+ number = [patient_mobile]
+ try:
+ send_sms(number, message)
+ except Exception as e:
+ frappe.msgprint(_('SMS not sent, please check SMS Settings'), alert=True)
@frappe.whitelist()
def get_events(start, end, filters=None):
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index 7075af5..eeed157 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -4,14 +4,15 @@
from __future__ import unicode_literals
import unittest
import frappe
-from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status
+from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
from frappe.utils import nowdate, add_days
from frappe.utils.make_random import get_random
class TestPatientAppointment(unittest.TestCase):
def setUp(self):
frappe.db.sql("""delete from `tabPatient Appointment`""")
- frappe.db.sql("""delete from `tabFee Validity""")
+ frappe.db.sql("""delete from `tabFee Validity`""")
+ frappe.db.sql("""delete from `tabPatient Encounter`""")
def test_status(self):
patient, medical_department, practitioner = create_healthcare_docs()
@@ -23,6 +24,19 @@
create_encounter(appointment)
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
+ def test_start_encounter(self):
+ patient, medical_department, practitioner = create_healthcare_docs()
+ frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
+ appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1)
+ self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1)
+ encounter = make_encounter(appointment.name)
+ self.assertTrue(encounter)
+ self.assertEqual(encounter.company, appointment.company)
+ self.assertEqual(encounter.practitioner, appointment.practitioner)
+ self.assertEqual(encounter.patient, appointment.patient)
+ # invoiced flag mapped from appointment
+ self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'))
+
def test_invoicing(self):
patient, medical_department, practitioner = create_healthcare_docs()
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
@@ -33,7 +47,11 @@
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), invoice=1)
self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1)
- self.assertTrue(frappe.db.get_value('Patient Appointment', appointment.name, 'ref_sales_invoice'))
+ sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
+ self.assertTrue(sales_invoice_name)
+ self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'company'), appointment.company)
+ self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'patient'), appointment.patient)
+ self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
def test_appointment_cancel(self):
patient, medical_department, practitioner = create_healthcare_docs()
@@ -53,8 +71,8 @@
appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
update_status(appointment.name, 'Cancelled')
# check invoice cancelled
- sales_invoice = frappe.db.get_value('Patient Appointment', appointment.name, 'ref_sales_invoice')
- self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice, 'status'), 'Cancelled')
+ sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
+ self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled')
def create_healthcare_docs():
@@ -90,14 +108,15 @@
patient = patient.name
return patient
-def create_encounter(appointment=None):
- encounter = frappe.new_doc('Patient Encounter')
+def create_encounter(appointment):
if appointment:
+ encounter = frappe.new_doc('Patient Encounter')
encounter.appointment = appointment.name
encounter.patient = appointment.patient
encounter.practitioner = appointment.practitioner
encounter.encounter_date = appointment.appointment_date
encounter.encounter_time = appointment.appointment_time
+ encounter.company = appointment.company
encounter.save()
encounter.submit()
return encounter
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
index 78e789d..2410f8e 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
@@ -25,15 +25,16 @@
refresh_field('lab_test_prescription');
if (!frm.doc.__islocal) {
-
- if (frm.doc.inpatient_status == 'Admission Scheduled' || frm.doc.inpatient_status == 'Admitted') {
- frm.add_custom_button(__('Schedule Discharge'), function() {
- schedule_discharge(frm);
- });
- } else if (frm.doc.inpatient_status != 'Discharge Scheduled') {
- frm.add_custom_button(__('Schedule Admission'), function() {
- schedule_inpatient(frm);
- });
+ if (frm.doc.docstatus === 1) {
+ if (frm.doc.inpatient_status == 'Admission Scheduled' || frm.doc.inpatient_status == 'Admitted') {
+ frm.add_custom_button(__('Schedule Discharge'), function() {
+ schedule_discharge(frm);
+ });
+ } else if (frm.doc.inpatient_status != 'Discharge Scheduled') {
+ frm.add_custom_button(__('Schedule Admission'), function() {
+ schedule_inpatient(frm);
+ });
+ }
}
frm.add_custom_button(__('Patient History'), function() {
@@ -101,6 +102,11 @@
frm.events.set_patient_info(frm);
},
+ practitioner: function(frm) {
+ if (!frm.doc.practitioner) {
+ frm.set_value('practitioner_name', '');
+ }
+ },
set_appointment_fields: function(frm) {
if (frm.doc.appointment) {
frappe.call({
@@ -114,9 +120,11 @@
'patient':data.message.patient,
'type': data.message.appointment_type,
'practitioner': data.message.practitioner,
- 'invoiced': data.message.invoiced
+ 'invoiced': data.message.invoiced,
+ 'company': data.message.company
};
frm.set_value(values);
+ frm.set_df_property('patient', 'read_only', 1);
}
});
}
@@ -133,6 +141,7 @@
'inpatient_status': ''
};
frm.set_value(values);
+ frm.set_df_property('patient', 'read_only', 0);
}
},
@@ -148,19 +157,25 @@
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);
- if (data.message.inpatient_record) {
- frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_record', data.message.inpatient_record);
- frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_status', data.message.inpatient_status);
- }
+ let values = {
+ 'patient_age': age,
+ 'patient_name':data.message.patient_name,
+ 'patient_sex': data.message.sex,
+ 'inpatient_record': data.message.inpatient_record,
+ 'inpatient_status': data.message.inpatient_status
+ };
+ frm.set_value(values);
}
});
} else {
- frappe.model.set_value(frm.doctype, frm.docname, 'patient_sex', '');
- frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', '');
- frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_record', '');
- frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_status', '');
+ let values = {
+ 'patient_age': '',
+ 'patient_name':'',
+ 'patient_sex': '',
+ 'inpatient_record': '',
+ 'inpatient_status': ''
+ };
+ frm.set_value(values);
}
}
});
@@ -212,8 +227,8 @@
}
frappe.route_options = {
'patient': frm.doc.patient,
- 'appointment': frm.doc.appointment,
- 'encounter': frm.doc.name
+ 'encounter': frm.doc.name,
+ 'company': frm.doc.company
};
frappe.new_doc('Vital Signs');
};
@@ -224,7 +239,8 @@
}
frappe.route_options = {
'patient': frm.doc.patient,
- 'medical_department': frm.doc.medical_department
+ 'medical_department': frm.doc.medical_department,
+ 'company': frm.doc.company
};
frappe.new_doc('Clinical Procedure');
};
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
index 5f11039..05eec87 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
@@ -11,23 +11,23 @@
"engine": "InnoDB",
"field_order": [
"naming_series",
+ "title",
"appointment",
"appointment_type",
"patient",
"patient_name",
"patient_sex",
"patient_age",
- "company",
+ "inpatient_record",
+ "inpatient_status",
"column_break_6",
- "practitioner",
- "medical_department",
+ "company",
"encounter_date",
"encounter_time",
+ "practitioner",
+ "practitioner_name",
+ "medical_department",
"invoiced",
- "section_break_1",
- "inpatient_record",
- "column_break_17",
- "inpatient_status",
"sb_symptoms",
"symptoms",
"symptoms_in_print",
@@ -47,6 +47,7 @@
"therapies",
"section_break_33",
"encounter_comment",
+ "sb_refs",
"amended_from"
],
"fields": [
@@ -58,12 +59,6 @@
"read_only": 1
},
{
- "collapsible": 1,
- "fieldname": "section_break_1",
- "fieldtype": "Section Break",
- "label": "Inpatient Details"
- },
- {
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
@@ -77,14 +72,13 @@
"ignore_user_permissions": 1,
"label": "Appointment",
"options": "Patient Appointment",
- "search_index": 1
+ "search_index": 1,
+ "set_only_once": 1
},
{
- "fetch_from": "inpatient_record.patient",
"fieldname": "patient",
"fieldtype": "Link",
"ignore_user_permissions": 1,
- "in_list_view": 1,
"in_standard_filter": 1,
"label": "Patient",
"options": "Patient",
@@ -92,7 +86,6 @@
"search_index": 1
},
{
- "fetch_from": "patient.patient_name",
"fieldname": "patient_name",
"fieldtype": "Data",
"label": "Patient Name",
@@ -114,7 +107,6 @@
{
"fieldname": "company",
"fieldtype": "Link",
- "hidden": 1,
"label": "Company",
"options": "Company"
},
@@ -125,7 +117,6 @@
{
"fieldname": "practitioner",
"fieldtype": "Link",
- "in_list_view": 1,
"in_standard_filter": 1,
"label": "Healthcare Practitioner",
"options": "Healthcare Practitioner",
@@ -207,29 +198,29 @@
{
"fieldname": "codification_table",
"fieldtype": "Table",
- "label": "Medical Coding",
+ "label": "Medical Codes",
"options": "Codification Table"
},
{
"fieldname": "sb_drug_prescription",
"fieldtype": "Section Break",
- "label": "Medication"
+ "label": "Medications"
},
{
"fieldname": "drug_prescription",
"fieldtype": "Table",
- "label": "Drug Prescription",
+ "label": "Items",
"options": "Drug Prescription"
},
{
"fieldname": "sb_test_prescription",
"fieldtype": "Section Break",
- "label": "Investigation"
+ "label": "Investigations"
},
{
"fieldname": "lab_test_prescription",
"fieldtype": "Table",
- "label": "Lab Prescription",
+ "label": "Lab Tests",
"options": "Lab Prescription"
},
{
@@ -240,7 +231,7 @@
{
"fieldname": "procedure_prescription",
"fieldtype": "Table",
- "label": "Procedure Prescription",
+ "label": "Clinical Procedures",
"no_copy": 1,
"options": "Procedure Prescription"
},
@@ -299,7 +290,6 @@
"fieldname": "medical_department",
"fieldtype": "Link",
"ignore_user_permissions": 1,
- "in_list_view": 1,
"in_standard_filter": 1,
"label": "Department",
"options": "Medical Department",
@@ -312,13 +302,31 @@
"read_only": 1
},
{
- "fieldname": "column_break_17",
- "fieldtype": "Column Break"
+ "fieldname": "sb_refs",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fetch_from": "practitioner.practitioner_name",
+ "fieldname": "practitioner_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Practitioner Name",
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Title",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-14 16:18:08.180457",
+ "modified": "2020-04-27 21:58:29.789797",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Encounter",
@@ -345,7 +353,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
- "title_field": "patient",
+ "title_field": "title",
"track_changes": 1,
"track_seen": 1
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
index 1734c28..56401a3 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
@@ -10,6 +10,9 @@
from frappe import _
class PatientEncounter(Document):
+ def validate(self):
+ self.set_title()
+
def on_update(self):
if self.appointment:
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
@@ -29,6 +32,10 @@
def on_submit(self):
create_therapy_plan(self)
+ def set_title(self):
+ self.title = _('{0} with {1}').format(self.patient_name or self.patient,
+ self.practitioner_name or self.practitioner)[:100]
+
def create_therapy_plan(encounter):
if len(encounter.therapies):
doc = frappe.new_doc('Therapy Plan')
diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json
index 39cead8..c352287 100644
--- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json
+++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.json
@@ -9,14 +9,14 @@
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
- "inpatient_record",
"naming_series",
- "invoiced",
"patient",
- "column_break_4",
"patient_age",
"patient_sex",
+ "column_break_4",
+ "inpatient_record",
"company",
+ "invoiced",
"section_break_6",
"sample",
"sample_uom",
@@ -167,7 +167,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-03-25 16:55:52.376834",
+ "modified": "2020-04-04 19:17:02.707203",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Sample Collection",
diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js
index abe4def..e66e667 100644
--- a/erpnext/healthcare/doctype/therapy_session/therapy_session.js
+++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.js
@@ -9,6 +9,16 @@
{fieldname: 'counts_completed', columns: 1},
{fieldname: 'assistance_level', columns: 1}
];
+
+ frm.set_query('service_unit', function() {
+ return {
+ filters: {
+ 'is_group': false,
+ 'allow_appointments': true,
+ 'company': frm.doc.company
+ }
+ };
+ });
},
refresh: function(frm) {
diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.json b/erpnext/healthcare/doctype/vital_signs/vital_signs.json
index 75726db..15ab504 100644
--- a/erpnext/healthcare/doctype/vital_signs/vital_signs.json
+++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.json
@@ -2,18 +2,22 @@
"actions": [],
"allow_copy": 1,
"allow_import": 1,
+ "autoname": "naming_series:",
"beta": 1,
"creation": "2017-02-02 11:00:24.853005",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "inpatient_record",
+ "naming_series",
+ "title",
"patient",
"patient_name",
+ "inpatient_record",
"appointment",
"encounter",
"column_break_2",
+ "company",
"signs_date",
"signs_time",
"sb_vs",
@@ -34,7 +38,7 @@
"bmi",
"column_break_14",
"nutrition_note",
- "company",
+ "sb_references",
"amended_from"
],
"fields": [
@@ -68,7 +72,8 @@
"fieldname": "appointment",
"fieldtype": "Link",
"in_filter": 1,
- "label": "Appointment",
+ "label": "Patient Appointment",
+ "no_copy": 1,
"options": "Patient Appointment",
"print_hide": 1,
"read_only": 1
@@ -81,8 +86,7 @@
"no_copy": 1,
"options": "Patient Encounter",
"print_hide": 1,
- "read_only": 1,
- "report_hide": 1
+ "read_only": 1
},
{
"fieldname": "column_break_2",
@@ -217,7 +221,6 @@
{
"fieldname": "company",
"fieldtype": "Link",
- "hidden": 1,
"label": "Company",
"options": "Company"
},
@@ -229,11 +232,34 @@
"options": "Vital Signs",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "sb_references",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "options": "HLC-VTS-.YYYY.-",
+ "reqd": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "columns": 5,
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Title",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-03-04 17:19:29.549889",
+ "modified": "2020-05-17 22:23:24.632286",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Vital Signs",
@@ -273,7 +299,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
- "title_field": "patient",
+ "title_field": "title",
"track_changes": 1,
"track_seen": 1
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.py b/erpnext/healthcare/doctype/vital_signs/vital_signs.py
index b0e78e8..69d81ff 100644
--- a/erpnext/healthcare/doctype/vital_signs/vital_signs.py
+++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.py
@@ -9,12 +9,19 @@
from frappe import _
class VitalSigns(Document):
+ def validate(self):
+ self.set_title()
+
def on_submit(self):
insert_vital_signs_to_medical_record(self)
def on_cancel(self):
delete_vital_signs_from_medical_record(self)
+ def set_title(self):
+ self.title = _('{0} on {1}').format(self.patient_name or self.patient,
+ frappe.utils.format_date(self.signs_date))[:100]
+
def insert_vital_signs_to_medical_record(doc):
subject = set_subject_field(doc)
medical_record = frappe.new_doc('Patient Medical Record')
diff --git a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
new file mode 100644
index 0000000..db35149
--- /dev/null
+++ b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
@@ -0,0 +1,42 @@
+{
+ "allow_roles": [
+ {
+ "role": "Healthcare Administrator"
+ }
+ ],
+ "creation": "2020-05-19 10:32:43.025852",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/healthcare",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-19 12:52:09.757729",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Healthcare",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Create Patient"
+ },
+ {
+ "step": "Create Practitioner"
+ },
+ {
+ "step": "Create Practitioner Schedule"
+ },
+ {
+ "step": "Setup Schedule and Employee for Healthcare Practitioner"
+ },
+ {
+ "step": "Explore Healthcare Settings"
+ },
+ {
+ "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
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/create_patient/create_patient.json b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json
new file mode 100644
index 0000000..77bc5bd
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 10:32:27.648902",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:26:24.023418",
+ "modified_by": "Administrator",
+ "name": "Create Patient",
+ "owner": "Administrator",
+ "reference_document": "Patient",
+ "show_full_form": 1,
+ "title": "Create Patient",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/create_practitioner/create_practitioner.json b/erpnext/healthcare/onboarding_step/create_practitioner/create_practitioner.json
new file mode 100644
index 0000000..614b201
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/create_practitioner/create_practitioner.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 10:39:55.728057",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:27:39.851375",
+ "modified_by": "Administrator",
+ "name": "Create Practitioner",
+ "owner": "Administrator",
+ "reference_document": "Healthcare Practitioner",
+ "show_full_form": 1,
+ "title": "Create Practitioner",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json
new file mode 100644
index 0000000..65980ef
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 10:41:19.065753",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:27:09.437825",
+ "modified_by": "Administrator",
+ "name": "Create Practitioner Schedule",
+ "owner": "Administrator",
+ "reference_document": "Practitioner Schedule",
+ "show_full_form": 1,
+ "title": "Create Practitioner Schedule",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json
new file mode 100644
index 0000000..f0c0f61
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json
@@ -0,0 +1,19 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-19 11:40:51.963741",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 11:46:35.085270",
+ "modified_by": "Administrator",
+ "name": "Explore Clinical Procedure Templates",
+ "owner": "Administrator",
+ "reference_document": "Clinical Procedure Template",
+ "show_full_form": 0,
+ "title": "Explore Clinical Procedure Templates",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json
new file mode 100644
index 0000000..2bdab69
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-19 11:14:33.044989",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:26:48.682673",
+ "modified_by": "Administrator",
+ "name": "Explore Healthcare Settings",
+ "owner": "Administrator",
+ "reference_document": "Healthcare Settings",
+ "show_full_form": 0,
+ "title": "Explore Healthcare Settings",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/setup_schedule_and_employee_for_healthcare_practitioner/setup_schedule_and_employee_for_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/setup_schedule_and_employee_for_healthcare_practitioner/setup_schedule_and_employee_for_healthcare_practitioner.json
new file mode 100644
index 0000000..c5af177
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/setup_schedule_and_employee_for_healthcare_practitioner/setup_schedule_and_employee_for_healthcare_practitioner.json
@@ -0,0 +1,20 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-19 10:43:56.231679",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "field": "schedule",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:26:42.492734",
+ "modified_by": "Administrator",
+ "name": "Setup Schedule and Employee for Healthcare Practitioner",
+ "owner": "Administrator",
+ "reference_document": "Healthcare Practitioner",
+ "show_full_form": 0,
+ "title": "Setup Schedule and Employee for Healthcare Practitioner",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index a756532..f092578 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -3,83 +3,84 @@
# For license information, please see license.txt
from __future__ import unicode_literals
+import math
import frappe
from frappe import _
-import math
from frappe.utils import time_diff_in_hours, rounded
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
@frappe.whitelist()
-def get_healthcare_services_to_invoice(patient):
+def get_healthcare_services_to_invoice(patient, company):
patient = frappe.get_doc('Patient', patient)
+ items_to_invoice = []
if patient:
validate_customer_created(patient)
- items_to_invoice = []
- patient_appointments = frappe.get_list(
- 'Patient Appointment',
- fields='*',
- filters={'patient': patient.name, 'invoiced': 0},
- order_by='appointment_date'
- )
- if patient_appointments:
- items_to_invoice = get_fee_validity(patient_appointments)
+ # Customer validated, build a list of billable services
+ items_to_invoice += get_appointments_to_invoice(patient, company)
+ items_to_invoice += get_encounters_to_invoice(patient, company)
+ items_to_invoice += get_lab_tests_to_invoice(patient, company)
+ items_to_invoice += get_clinical_procedures_to_invoice(patient, company)
+ items_to_invoice += get_inpatient_services_to_invoice(patient, company)
+ items_to_invoice += get_therapy_sessions_to_invoice(patient, company)
- encounters = get_encounters_to_invoice(patient)
- lab_tests = get_lab_tests_to_invoice(patient)
- clinical_procedures = get_clinical_procedures_to_invoice(patient)
- inpatient_services = get_inpatient_services_to_invoice(patient)
- therapy_sessions = get_therapy_sessions_to_invoice(patient)
- items_to_invoice += encounters + lab_tests + clinical_procedures + inpatient_services + therapy_sessions
return items_to_invoice
+
def validate_customer_created(patient):
if not frappe.db.get_value('Patient', patient.name, 'customer'):
msg = _("Please set a Customer linked to the Patient")
msg += " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(patient.name)
frappe.throw(msg, title=_('Customer Not Found'))
-def get_fee_validity(patient_appointments):
- if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'):
- return []
+def get_appointments_to_invoice(patient, company):
+ appointments_to_invoice = []
+ patient_appointments = frappe.get_list(
+ 'Patient Appointment',
+ fields = '*',
+ filters = {'patient': patient.name, 'company': company, 'invoiced': 0},
+ order_by = 'appointment_date'
+ )
- items_to_invoice = []
for appointment in patient_appointments:
+ # Procedure Appointments
if appointment.procedure_template:
if frappe.db.get_value('Clinical Procedure Template', appointment.procedure_template, 'is_billable'):
- items_to_invoice.append({
+ appointments_to_invoice.append({
'reference_type': 'Patient Appointment',
'reference_name': appointment.name,
'service': appointment.procedure_template
})
+ # Consultation Appointments, should check fee validity
else:
- fee_validity = frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name})
- if not fee_validity:
- practitioner_charge = 0
- income_account = None
- service_item = None
- if appointment.practitioner:
- service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment)
- income_account = get_income_account(appointment.practitioner, appointment.company)
- items_to_invoice.append({
- 'reference_type': 'Patient Appointment',
- 'reference_name': appointment.name,
- 'service': service_item,
- 'rate': practitioner_charge,
- 'income_account': income_account
- })
+ if frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') and \
+ frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}):
+ continue # Skip invoicing, fee validty present
+ practitioner_charge = 0
+ income_account = None
+ service_item = None
+ if appointment.practitioner:
+ service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment)
+ income_account = get_income_account(appointment.practitioner, appointment.company)
+ appointments_to_invoice.append({
+ 'reference_type': 'Patient Appointment',
+ 'reference_name': appointment.name,
+ 'service': service_item,
+ 'rate': practitioner_charge,
+ 'income_account': income_account
+ })
- return items_to_invoice
+ return appointments_to_invoice
-def get_encounters_to_invoice(patient):
+def get_encounters_to_invoice(patient, company):
encounters_to_invoice = []
encounters = frappe.get_list(
'Patient Encounter',
fields=['*'],
- filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1}
+ filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1}
)
if encounters:
for encounter in encounters:
@@ -102,12 +103,12 @@
return encounters_to_invoice
-def get_lab_tests_to_invoice(patient):
+def get_lab_tests_to_invoice(patient, company):
lab_tests_to_invoice = []
lab_tests = frappe.get_list(
'Lab Test',
fields=['name', 'template'],
- filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1}
+ filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1}
)
for lab_test in lab_tests:
item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.template, ['item', 'is_billable'])
@@ -143,12 +144,12 @@
return lab_tests_to_invoice
-def get_clinical_procedures_to_invoice(patient):
+def get_clinical_procedures_to_invoice(patient, company):
clinical_procedures_to_invoice = []
procedures = frappe.get_list(
'Clinical Procedure',
fields='*',
- filters={'patient': patient.name, 'invoiced': False}
+ filters={'patient': patient.name, 'company': company, 'invoiced': False}
)
for procedure in procedures:
if not procedure.appointment:
@@ -204,7 +205,7 @@
return clinical_procedures_to_invoice
-def get_inpatient_services_to_invoice(patient):
+def get_inpatient_services_to_invoice(patient, company):
services_to_invoice = []
inpatient_services = frappe.db.sql(
'''
@@ -214,10 +215,11 @@
`tabInpatient Record` ip, `tabInpatient Occupancy` io
WHERE
ip.patient=%s
+ and ip.company=%s
and io.parent=ip.name
and io.left=1
and io.invoiced=0
- ''', (patient.name), as_dict=1)
+ ''', (patient.name, company), as_dict=1)
for inpatient_occupancy in inpatient_services:
service_unit_type = frappe.db.get_value('Healthcare Service Unit', inpatient_occupancy.service_unit, 'service_unit_type')
@@ -244,12 +246,12 @@
return services_to_invoice
-def get_therapy_sessions_to_invoice(patient):
+def get_therapy_sessions_to_invoice(patient, company):
therapy_sessions_to_invoice = []
therapy_sessions = frappe.get_list(
'Therapy Session',
fields='*',
- filters={'patient': patient.name, 'invoiced': False}
+ filters={'patient': patient.name, 'invoiced': 0, 'company': company}
)
for therapy in therapy_sessions:
if not therapy.appointment:
@@ -396,6 +398,7 @@
def manage_fee_validity(appointment):
fee_validity = check_fee_validity(appointment)
+
if fee_validity:
if appointment.status == 'Cancelled' and fee_validity.visited > 0:
fee_validity.visited -= 1
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 6b198e7..ab161aa 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -308,7 +308,8 @@
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
- "erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status"
+ "erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
+ "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status"
],
"daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send",
diff --git a/erpnext/hr/dashboard_fixtures.py b/erpnext/hr/dashboard_fixtures.py
index c608e2b..004477c 100644
--- a/erpnext/hr/dashboard_fixtures.py
+++ b/erpnext/hr/dashboard_fixtures.py
@@ -4,6 +4,7 @@
import frappe
import erpnext
import json
+from frappe import _
def get_data():
return frappe._dict({
@@ -23,24 +24,24 @@
"dashboard_name": "Human Resource",
"is_default": 1,
"charts": [
+ { "chart": "Outgoing Salary", "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"},
- { "chart": "Outgoing Salary", "width": "Full"},
{ "chart": "Attendance Count", "width": "Full"}
],
"cards": [
{"card": "Total Employees"},
- {"card": "New Joinees"},
- {'card': "Employees Left"},
- {'card': "Total Job Openings"},
- {'card': "Total Applicants"},
- {'card': "Short Listed Candidates"},
- {'card': "Rejected Candidates"},
- {'card': "Total Job Offered"},
+ {"card": "New Joinees (Last year)"},
+ {'card': "Employees Left (Last year)"},
+ {'card': "Total Job Openings (Last month)"},
+ {'card': "Total Applicants (Last month)"},
+ {'card': "Shortlisted Candidates (Last month)"},
+ {'card': "Rejected Candidates (Last month)"},
+ {'card': "Total Job Offered (Last month)"},
]
}
@@ -59,36 +60,65 @@
company = frappe.db.get_value("Company", {"is_group": 0}, "name")
dashboard_charts = [
- get_dashboards_chart_doc('Gender Diversity Ratio', "Group By", "Donut",document_type = "Employee", group_by_type="Count", group_by_based_on="gender", filters_json = json.dumps([["Employee","status","=","Active"]]), time_interval = "Monthly")
+ 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", "Donut",document_type = "Job Applicant", group_by_type="Count", group_by_based_on="status", filters_json = json.dumps([["Job Applicant","creation","Previous","1 month"]]), time_interval = "Monthly")
+ 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"]]))
)
dashboard_charts.append(
- get_dashboards_chart_doc('Outgoing Salary', "Sum", "Line",document_type = "Salary Slip", group_by_type="Count", based_on="end_date", value_based_on = "rounded_total", time_interval = "Monthly", timeseries = 1 , filters_json = json.dumps([["Salary Slip","docstatus","=","1"]]))
+ 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]]))
)
- custom_options = '''{"type": "line", "lineOptions": {"dotSize": 6}, "axisOptions": {"shortenYAxisNumbers": 1}, "tooltipOptions": {}}'''
- filters_json = json.dumps({"month": month_map[date.month - 1], "year": str(date.year), "company":company})
+
+ 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)
+ 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"]]), time_interval = "Monthly")
+ 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"]]), time_interval = "Monthly")
+ 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")
+ 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")
+ 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
@@ -104,7 +134,7 @@
]
number_cards.append(
- get_number_cards_doc("Employee", "New Joinees", filters_json = json.dumps([
+ get_number_cards_doc("Employee", "New Joinees (Last year)", filters_json = json.dumps([
["Employee","date_of_joining","Previous","1 year"],
["Employee","status","=","Active"]
])
@@ -112,40 +142,43 @@
)
number_cards.append(
- get_number_cards_doc("Employee", "Employees Left", filters_json = json.dumps([
- ["Employee","modified","Previous","1 year"],
- ["Employee","status","=","Left"]
+ get_number_cards_doc("Employee", "Employees Left (Last year)", filters_json = json.dumps([
+ ["Employee", "modified", "Previous", "1 year"],
+ ["Employee", "status", "=", "Left"]
])
)
)
number_cards.append(
- get_number_cards_doc("Job Applicant", "Total Applicants", filters_json = json.dumps([
- ["Job Applicant","creation","Previous","1 month"]
+ get_number_cards_doc("Job Applicant", "Total Applicants (Last month)", filters_json = json.dumps([
+ ["Job Applicant", "creation", "Previous", "1 month"]
])
)
)
number_cards.append(
- get_number_cards_doc("Job Opening", "Total Job Openings", func = "Sum",aggregate_function_based_on = "planned_vacancies", filters_json = json.dumps([["Job Opening","creation","Previous","1 month"]])
+ get_number_cards_doc("Job Opening", "Total Job Openings (Last month)", func = "Sum",
+ aggregate_function_based_on = "planned_vacancies",
+ filters_json = json.dumps([["Job Opening", "creation", "Previous", "1 month"]])
)
)
number_cards.append(
- get_number_cards_doc("Job Applicant", "Short Listed Candidates", filters_json = json.dumps([
- ["Job Applicant","status","=","Accepted"],
- ["Job Applicant","creation","Previous","1 month"]
+ get_number_cards_doc("Job Applicant", "Shortlisted Candidates (Last month)", filters_json = json.dumps([
+ ["Job Applicant", "status", "=", "Accepted"],
+ ["Job Applicant", "creation", "Previous", "1 month"]
])
)
)
number_cards.append(
- get_number_cards_doc("Job Applicant", "Rejected Candidates", filters_json = json.dumps([
- ["Job Applicant","status","=","Rejected"],
- ["Job Applicant","creation","Previous","1 month"]
+ get_number_cards_doc("Job Applicant", "Rejected Candidates (Last month)", filters_json = json.dumps([
+ ["Job Applicant", "status", "=", "Rejected"],
+ ["Job Applicant", "creation", "Previous", "1 month"]
])
)
)
number_cards.append(
- get_number_cards_doc("Job Offer", "Total Job Offered", filters_json = json.dumps([["Job Offer","creation","Previous","1 month"]])
+ get_number_cards_doc("Job Offer", "Total Job Offered (Last month)",
+ filters_json = json.dumps([["Job Offer", "creation", "Previous", "1 month"]])
)
)
@@ -160,7 +193,7 @@
"document_type": document_type,
"function": args.func or "Count",
"is_public": args.is_public or 1,
- "label": label,
+ "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',
@@ -169,12 +202,11 @@
}
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_name": _(args.chart_name or name),
"chart_type": chart_type,
"document_type": args.document_type or None,
"report_name": args.report_name or None,
diff --git a/erpnext/hr/desk_page/hr/hr.json b/erpnext/hr/desk_page/hr/hr.json
index 570596e..2d0e885 100644
--- a/erpnext/hr/desk_page/hr/hr.json
+++ b/erpnext/hr/desk_page/hr/hr.json
@@ -89,10 +89,11 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "HR",
- "modified": "2020-05-14 12:14:21.682026",
+ "modified": "2020-05-20 11:20:54.255557",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
@@ -126,16 +127,16 @@
"type": "DocType"
},
{
- "format": "{} Open",
- "label": "Job Applicant",
- "link_to": "Job Applicant",
- "stats_filter": "{\n \"status\": \"Open\"\n}",
- "type": "DocType"
- },
- {
"label": "Salary Register",
"link_to": "Monthly Attendance Sheet",
"type": "Report"
+ },
+ {
+ "format": "{} Open",
+ "label": "HR Dashboard",
+ "link_to": "Human Resource",
+ "stats_filter": "{\n \"status\": \"Open\"\n}",
+ "type": "Dashboard"
}
]
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index ba804ce..45b7060 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -21,7 +21,7 @@
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
# leaves can be marked for future dates
- if self.status not in ('On Leave', 'Half Day') and getdate(self.attendance_date) > getdate(nowdate()):
+ if self.status != 'On Leave' and not self.leave_application and getdate(self.attendance_date) > getdate(nowdate()):
frappe.throw(_("Attendance can not be marked for future dates"))
elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining):
frappe.throw(_("Attendance date can not be less than employee's joining date"))
diff --git a/erpnext/hr/doctype/department/department.json b/erpnext/hr/doctype/department/department.json
index 6469f4c..a54c1d1 100644
--- a/erpnext/hr/doctype/department/department.json
+++ b/erpnext/hr/doctype/department/department.json
@@ -14,6 +14,8 @@
"is_group",
"disabled",
"section_break_4",
+ "payroll_cost_center",
+ "column_break_9",
"leave_block_list",
"leave_section",
"leave_approvers",
@@ -125,13 +127,23 @@
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "payroll_cost_center",
+ "fieldtype": "Link",
+ "label": "Payroll Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-sitemap",
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2020-03-18 18:03:27.784362",
+ "modified": "2020-05-05 18:49:28.503931",
"modified_by": "Administrator",
"module": "HR",
"name": "Department",
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 13c202c..f575765 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -60,6 +60,8 @@
"default_shift",
"salary_information",
"salary_mode",
+ "payroll_cost_center",
+ "column_break_52",
"bank_name",
"bank_ac_no",
"health_insurance_section",
@@ -783,13 +785,25 @@
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "department.payroll_cost_center",
+ "fetch_if_empty": 1,
+ "fieldname": "payroll_cost_center",
+ "fieldtype": "Link",
+ "label": "Payroll Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "column_break_52",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-user",
"idx": 24,
"image_field": "image",
"links": [],
- "modified": "2020-04-08 12:25:34.306695",
+ "modified": "2020-05-05 18:51:03.152503",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index d3410de..f4b214a 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -45,7 +45,7 @@
employee1_doc.status = 'Left'
self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
-def make_employee(user, company=None):
+def make_employee(user, company=None, **kwargs):
if not frappe.db.get_value("User", user):
frappe.get_doc({
"doctype": "User",
@@ -55,7 +55,7 @@
"roles": [{"doctype": "Has Role", "role": "Employee"}]
}).insert()
- if not frappe.db.get_value("Employee", { "user_id": user, "company": company or erpnext.get_default_company() }):
+ if not frappe.db.get_value("Employee", {"user_id": user}):
employee = frappe.get_doc({
"doctype": "Employee",
"naming_series": "EMP-",
@@ -71,7 +71,10 @@
"prefered_email": user,
"status": "Active",
"employment_type": "Intern"
- }).insert()
+ })
+ if kwargs:
+ employee.update(kwargs)
+ employee.insert()
return employee.name
else:
return frappe.get_value("Employee", {"employee_name":user}, "name")
diff --git a/erpnext/hr/doctype/employee_other_income/employee_other_income.json b/erpnext/hr/doctype/employee_other_income/employee_other_income.json
index 2dd6c10..8abfe1e 100644
--- a/erpnext/hr/doctype/employee_other_income/employee_other_income.json
+++ b/erpnext/hr/doctype/employee_other_income/employee_other_income.json
@@ -76,25 +76,15 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-03-19 18:06:45.361830",
+ "modified": "2020-05-14 17:17:38.883126",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Other Income",
"owner": "Administrator",
"permissions": [
{
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- },
- {
+ "amend": 1,
+ "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -104,9 +94,12 @@
"report": 1,
"role": "HR Manager",
"share": 1,
+ "submit": 1,
"write": 1
},
{
+ "amend": 1,
+ "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -116,9 +109,12 @@
"report": 1,
"role": "HR User",
"share": 1,
+ "submit": 1,
"write": 1
},
{
+ "amend": 1,
+ "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -128,6 +124,7 @@
"report": 1,
"role": "Employee",
"share": 1,
+ "submit": 1,
"write": 1
}
],
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index ac1bfa1..ea469b8 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -116,8 +116,9 @@
"party_type": "Employee",
"party": self.employee,
"against_voucher_type": self.doctype,
- "against_voucher": self.name
- })
+ "against_voucher": self.name,
+ "cost_center": self.cost_center
+ }, item=self)
)
# expense entries
@@ -129,7 +130,7 @@
"debit_in_account_currency": data.sanctioned_amount,
"against": self.employee,
"cost_center": data.cost_center
- })
+ }, item=data)
)
for data in self.advances:
@@ -157,7 +158,7 @@
"credit": self.grand_total,
"credit_in_account_currency": self.grand_total,
"against": self.employee
- })
+ }, item=self)
)
gl_entry.append(
@@ -170,7 +171,7 @@
"debit_in_account_currency": self.grand_total,
"against_voucher": self.name,
"against_voucher_type": self.doctype,
- })
+ }, item=self)
)
return gl_entry
@@ -187,7 +188,7 @@
"cost_center": self.cost_center,
"against_voucher_type": self.doctype,
"against_voucher": self.name
- })
+ }, item=tax)
)
def validate_account_details(self):
diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
index 16e9eef..3cce50e 100644
--- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
+++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
@@ -13,9 +13,11 @@
"description",
"section_break_6",
"amount",
- "cost_center",
"column_break_8",
- "sanctioned_amount"
+ "sanctioned_amount",
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break"
],
"fields": [
{
@@ -104,12 +106,21 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
+ },
+ {
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2019-12-11 13:42:33.233432",
+ "modified": "2020-05-11 18:54:35.601592",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim Detail",
diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
index d68caf1..885e3ee 100644
--- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
+++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
@@ -8,14 +8,16 @@
"engine": "InnoDB",
"field_order": [
"account_head",
- "cost_center",
"rate",
"col_break1",
"description",
"section_break_6",
"tax_amount",
"column_break_8",
- "total"
+ "total",
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break"
],
"fields": [
{
@@ -91,11 +93,20 @@
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
}
],
"istable": 1,
"links": [],
- "modified": "2020-03-11 13:25:06.721917",
+ "modified": "2020-05-11 19:01:26.611758",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Taxes and Charges",
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index 9161ed8..ebf8723 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -13,12 +13,12 @@
"stop_birthday_reminders",
"expense_approver_mandatory_in_expense_claim",
"payroll_settings",
- "payroll_based_on",
- "max_working_hours_against_timesheet",
+ "payroll_based_on",
+ "max_working_hours_against_timesheet",
"include_holidays_in_total_working_days",
"disable_rounded_total",
"column_break_11",
- "daily_wages_fraction_for_half_day",
+ "daily_wages_fraction_for_half_day",
"email_salary_slip_to_employee",
"encrypt_salary_slips_in_emails",
"password_policy",
@@ -191,7 +191,7 @@
"default": "Leave",
"fieldname": "payroll_based_on",
"fieldtype": "Select",
- "label": "Calculate Working Days in Payroll based on",
+ "label": "Calculate Payroll Working Days Based On",
"options": "Leave\nAttendance"
},
{
@@ -206,7 +206,7 @@
"idx": 1,
"issingle": 1,
"links": [],
- "modified": "2020-04-13 21:20:59.382394",
+ "modified": "2020-05-11 13:02:51.274347",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index d3a08cd..84f2c83 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -551,7 +551,7 @@
return _get_remaining_leaves(total_leaves, allocation.to_date)
-def get_leaves_for_period(employee, leave_type, from_date, to_date):
+def get_leaves_for_period(employee, leave_type, from_date, to_date, do_not_skip_expired_leaves=False):
leave_entries = get_leave_entries(employee, leave_type, from_date, to_date)
leave_days = 0
@@ -561,8 +561,8 @@
if inclusive_period and leave_entry.transaction_type == 'Leave Encashment':
leave_days += leave_entry.leaves
- elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \
- and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date):
+ elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' and leave_entry.is_expired \
+ and (do_not_skip_expired_leaves or not skip_expiry_leaves(leave_entry, to_date)):
leave_days += leave_entry.leaves
elif leave_entry.transaction_type == 'Leave Application':
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
index 9ed58c9..63559c4 100644
--- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
@@ -88,32 +88,40 @@
}, fieldname=['name'])
def process_expired_allocation():
- ''' Check if a carry forwarded allocation has expired and create a expiry ledger entry '''
+ ''' Check if a carry forwarded allocation has expired and create a expiry ledger entry
+ Case 1: carry forwarded expiry period is set for the leave type,
+ create a separate leave expiry entry against each entry of carry forwarded and non carry forwarded leaves
+ Case 2: leave type has no specific expiry period for carry forwarded leaves
+ and there is no carry forwarded leave allocation, create a single expiry against the remaining leaves.
+ '''
# fetch leave type records that has carry forwarded leaves expiry
leave_type_records = frappe.db.get_values("Leave Type", filters={
'expire_carry_forwarded_leaves_after_days': (">", 0)
}, fieldname=['name'])
- leave_type = [record[0] for record in leave_type_records]
+ leave_type = [record[0] for record in leave_type_records] or ['']
- expired_allocation = frappe.db.sql_list("""SELECT name
- FROM `tabLeave Ledger Entry`
- WHERE
- `transaction_type`='Leave Allocation'
- AND `is_expired`=1""")
-
- expire_allocation = frappe.get_all("Leave Ledger Entry",
- fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
- filters={
- 'to_date': ("<", today()),
- 'transaction_type': 'Leave Allocation',
- 'transaction_name': ('not in', expired_allocation)
- },
- or_filters={
- 'is_carry_forward': 0,
- 'leave_type': ('in', leave_type)
- })
+ # fetch non expired leave ledger entry of transaction_type allocation
+ expire_allocation = frappe.db.sql("""
+ SELECT
+ leaves, to_date, employee, leave_type,
+ is_carry_forward, transaction_name as name, transaction_type
+ FROM `tabLeave Ledger Entry` l
+ WHERE (NOT EXISTS
+ (SELECT name
+ FROM `tabLeave Ledger Entry`
+ WHERE
+ transaction_name = l.transaction_name
+ AND transaction_type = 'Leave Allocation'
+ AND name<>l.name
+ AND docstatus = 1
+ AND (
+ is_carry_forward=l.is_carry_forward
+ OR (is_carry_forward = 0 AND leave_type not in %s)
+ )))
+ AND transaction_type = 'Leave Allocation'
+ AND to_date < %s""", (leave_type, today()), as_dict=1)
if expire_allocation:
create_expiry_ledger_entry(expire_allocation)
@@ -133,6 +141,7 @@
'employee': allocation.employee,
'leave_type': allocation.leave_type,
'to_date': ('<=', allocation.to_date),
+ 'docstatus': 1
}, fieldname=['SUM(leaves)'])
@frappe.whitelist()
@@ -159,7 +168,8 @@
def expire_carried_forward_allocation(allocation):
''' Expires remaining leaves in the on carried forward allocation '''
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
- leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date)
+ leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type,
+ allocation.from_date, allocation.to_date, do_not_skip_expired_leaves=True)
leaves = flt(allocation.leaves) + flt(leaves_taken)
# allow expired leaves entry to be created
diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py
index 9ef3a99..656de01 100644
--- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py
@@ -55,6 +55,7 @@
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
{condition}""".format(condition=condition),
{"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
+
if sal_struct:
cond += "and t2.salary_structure IN %(sal_struct)s "
cond += "and %(from_date)s >= t2.from_date"
@@ -138,7 +139,7 @@
cond = self.get_filter_condition()
ss_list = frappe.db.sql("""
- select t1.name, t1.salary_structure from `tabSalary Slip` t1
+ select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1
where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s
""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
@@ -169,10 +170,14 @@
def get_salary_components(self, component_type):
salary_slips = self.get_sal_slip_list(ss_status = 1, as_dict = True)
- if salary_slips:
- salary_components = frappe.db.sql("""select salary_component, amount, parentfield
- from `tabSalary Detail` where parentfield = '%s' and parent in (%s)""" %
- (component_type, ', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=True)
+ if salary_slips:
+ salary_components = frappe.db.sql("""
+ select ssd.salary_component, ssd.amount, ssd.parentfield, ss.payroll_cost_center
+ from `tabSalary Slip` ss, `tabSalary Detail` ssd
+ where ss.name = ssd.parent and ssd.parentfield = '%s' and ss.name in (%s)
+ """ % (component_type, ', '.join(['%s']*len(salary_slips))),
+ tuple([d.name for d in salary_slips]), as_dict=True)
+
return salary_components
def get_salary_component_total(self, component_type = None):
@@ -186,15 +191,16 @@
if is_flexible_benefit == 1 and only_tax_impact ==1:
add_component_to_accrual_jv_entry = False
if add_component_to_accrual_jv_entry:
- component_dict[item['salary_component']] = component_dict.get(item['salary_component'], 0) + item['amount']
+ component_dict[(item.salary_component, item.payroll_cost_center)] \
+ = component_dict.get((item.salary_component, item.payroll_cost_center), 0) + flt(item.amount)
account_details = self.get_account(component_dict = component_dict)
return account_details
def get_account(self, component_dict = None):
- account_dict = {}
- for s, a in component_dict.items():
- account = self.get_salary_component_account(s)
- account_dict[account] = account_dict.get(account, 0) + a
+ account_dict = {}
+ for key, amount in component_dict.items():
+ account = self.get_salary_component_account(key[0])
+ account_dict[(account, key[1])] = account_dict.get((account, key[1]), 0) + amount
return account_dict
def get_default_payroll_payable_account(self):
@@ -227,23 +233,23 @@
payable_amount = 0
# Earnings
- for acc, amount in earnings.items():
+ for acc_cc, amount in earnings.items():
payable_amount += flt(amount, precision)
accounts.append({
- "account": acc,
+ "account": acc_cc[0],
"debit_in_account_currency": flt(amount, precision),
"party_type": '',
- "cost_center": self.cost_center,
+ "cost_center": acc_cc[1] or self.cost_center,
"project": self.project
})
# Deductions
- for acc, amount in deductions.items():
+ for acc_cc, amount in deductions.items():
payable_amount -= flt(amount, precision)
accounts.append({
- "account": acc,
+ "account": acc_cc[0],
"credit_in_account_currency": flt(amount, precision),
- "cost_center": self.cost_center,
+ "cost_center": acc_cc[1] or self.cost_center,
"party_type": '',
"project": self.project
})
@@ -253,6 +259,7 @@
"account": default_payroll_payable_account,
"credit_in_account_currency": flt(payable_amount, precision),
"party_type": '',
+ "cost_center": self.cost_center
})
journal_entry.set("accounts", accounts)
diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py
index e43f744..3c318e7 100644
--- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py
@@ -10,15 +10,16 @@
from erpnext.hr.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.salary_slip.test_salary_slip import get_salary_component_account, \
- make_earning_salary_component, make_deduction_salary_component
+ make_earning_salary_component, make_deduction_salary_component, create_account
from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure
from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
class TestPayrollEntry(unittest.TestCase):
def setUp(self):
- for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry", "Salary Structure"]:
- frappe.db.sql("delete from `tab%s`" % dt)
+ for dt in ["Salary Slip", "Salary Component", "Salary Component Account",
+ "Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]:
+ frappe.db.sql("delete from `tab%s`" % dt)
make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_salary_component(setup=True, company_list=["_Test Company"])
@@ -33,11 +34,59 @@
get_salary_component_account(data.name)
employee = frappe.db.get_value("Employee", {'company': company})
- make_salary_structure("_Test Salary Structure", "Monthly", employee)
+ make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company)
dates = get_start_end_dates('Monthly', nowdate())
if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date)
+ def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use
+ for data in frappe.get_all('Salary Component', fields = ["name"]):
+ if not frappe.db.get_value('Salary Component Account',
+ {'parent': data.name, 'company': "_Test Company"}, 'name'):
+ get_salary_component_account(data.name)
+
+ if not frappe.db.exists('Department', "cc - _TC"):
+ frappe.get_doc({
+ 'doctype': 'Department',
+ 'department_name': "cc",
+ "company": "_Test Company"
+ }).insert()
+
+ employee1 = make_employee("test_employee1@example.com", payroll_cost_center="_Test Cost Center - _TC",
+ department="cc - _TC", company="_Test Company")
+ employee2 = make_employee("test_employee2@example.com", payroll_cost_center="_Test Cost Center 2 - _TC",
+ department="cc - _TC", company="_Test Company")
+
+ make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company")
+ make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company")
+
+ if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"):
+ create_account(account_name="_Test Payroll Payable",
+ company="_Test Company", parent_account="Current Liabilities - _TC")
+ frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
+ "_Test Payroll Payable - _TC")
+
+ dates = get_start_end_dates('Monthly', nowdate())
+ if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
+ pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
+ department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC")
+ je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
+ je_entries = frappe.db.sql("""
+ select account, cost_center, debit, credit
+ from `tabJournal Entry Account`
+ where parent=%s
+ order by account, cost_center
+ """, je)
+ expected_je = (
+ ('_Test Payroll Payable - _TC', 'Main - _TC', 0.0, 155600.0),
+ ('Salary - _TC', '_Test Cost Center - _TC', 78000.0, 0.0),
+ ('Salary - _TC', '_Test Cost Center 2 - _TC', 78000.0, 0.0),
+ ('Salary Deductions - _TC', '_Test Cost Center - _TC', 0.0, 200.0),
+ ('Salary Deductions - _TC', '_Test Cost Center 2 - _TC', 0.0, 200.0)
+ )
+
+ self.assertEqual(je_entries, expected_je)
+
def test_get_end_date(self):
self.assertEqual(get_end_date('2017-01-01', 'monthly'), {'end_date': '2017-01-31'})
self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'})
@@ -49,7 +98,6 @@
self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'})
def test_loan(self):
-
branch = "Test Employee Branch"
applicant = make_employee("test_employee@loan.com", company="_Test Company")
company = "_Test Company"
@@ -116,6 +164,7 @@
payroll_entry.posting_date = nowdate()
payroll_entry.payroll_frequency = "Monthly"
payroll_entry.branch = args.branch or None
+ payroll_entry.department = args.department or None
if args.cost_center:
payroll_entry.cost_center = args.cost_center
@@ -123,6 +172,7 @@
if args.payment_account:
payroll_entry.payment_account = args.payment_account
+ payroll_entry.fill_employee_details()
payroll_entry.save()
payroll_entry.create_salary_slips()
payroll_entry.submit_salary_slips()
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json
index 54a8164..cfd4d89 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.json
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.json
@@ -12,6 +12,7 @@
"department",
"designation",
"branch",
+ "payroll_cost_center",
"column_break1",
"status",
"journal_entry",
@@ -459,13 +460,22 @@
"options": "Salary Slip",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fetch_from": "employee.payroll_cost_center",
+ "fetch_if_empty": 1,
+ "fieldname": "payroll_cost_center",
+ "fieldtype": "Link",
+ "label": "Payroll Cost Center",
+ "options": "Cost Center",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 9,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-14 20:02:53.159827",
+ "modified": "2020-05-05 18:55:26.173629",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Slip",
diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
index a7dcb94..3eff738 100644
--- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
@@ -422,22 +422,32 @@
sal_comp = frappe.get_doc("Salary Component", sal_comp)
if not sal_comp.get("accounts"):
for d in company_list:
+ company_abbr = frappe.get_cached_value('Company', d, 'abbr')
+
+ if sal_comp.type == "Earning":
+ account_name = "Salary"
+ parent_account = "Indirect Expenses - " + company_abbr
+ else:
+ account_name = "Salary Deductions"
+ parent_account = "Current Liabilities - " + company_abbr
+
sal_comp.append("accounts", {
"company": d,
- "default_account": create_account(d)
+ "default_account": create_account(account_name, d, parent_account)
})
sal_comp.save()
-def create_account(company):
- salary_account = frappe.db.get_value("Account", "Salary - " + frappe.get_cached_value('Company', company, 'abbr'))
- if not salary_account:
+def create_account(account_name, company, parent_account):
+ company_abbr = frappe.get_cached_value('Company', company, 'abbr')
+ account = frappe.db.get_value("Account", account_name + " - " + company_abbr)
+ if not account:
frappe.get_doc({
"doctype": "Account",
- "account_name": "Salary",
- "parent_account": "Indirect Expenses - " + frappe.get_cached_value('Company', company, 'abbr'),
+ "account_name": account_name,
+ "parent_account": parent_account,
"company": company
}).insert()
- return salary_account
+ return account
def make_earning_salary_component(setup=False, test_tax=False, company_list=None):
data = [
@@ -683,7 +693,7 @@
make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_salary_component(setup=True, company_list=["_Test Company"])
- for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance"]:
+ for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance", "Additional Salary"]:
frappe.db.sql("delete from `tab%s`" % dt)
make_holiday_list()
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py
index 5ba7f1c..ffc16d7 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.py
@@ -153,12 +153,16 @@
def postprocess(source, target):
if employee:
employee_details = frappe.db.get_value("Employee", employee,
- ["employee_name", "branch", "designation", "department"], as_dict=1)
+ ["employee_name", "branch", "designation", "department", "payroll_cost_center"], as_dict=1)
target.employee = employee
target.employee_name = employee_details.employee_name
target.branch = employee_details.branch
target.designation = employee_details.designation
target.department = employee_details.department
+ target.payroll_cost_center = employee_details.payroll_cost_center
+ if not target.payroll_cost_center and target.department:
+ target.payroll_cost_center = frappe.db.get_value("Department", target.department, "payroll_cost_center")
+
target.run_method('process_salary_structure', for_preview=for_preview)
doc = get_mapped_doc("Salary Structure", source_name, {
diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
index c1869f0..eb5311e 100644
--- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
@@ -128,6 +128,7 @@
salary_structure_doc.insert()
if not dont_submit:
salary_structure_doc.submit()
+
else:
salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure)
diff --git a/erpnext/hr/onboarding/human_resource/human_resource.json b/erpnext/hr/module_onboarding/human_resource/human_resource.json
similarity index 83%
rename from erpnext/hr/onboarding/human_resource/human_resource.json
rename to erpnext/hr/module_onboarding/human_resource/human_resource.json
index 45dd50d..e64582b 100644
--- a/erpnext/hr/onboarding/human_resource/human_resource.json
+++ b/erpnext/hr/module_onboarding/human_resource/human_resource.json
@@ -9,11 +9,11 @@
],
"creation": "2020-05-14 11:51:45.050242",
"docstatus": 0,
- "doctype": "Onboarding",
+ "doctype": "Module Onboarding",
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/human-resources",
"idx": 0,
"is_complete": 0,
- "modified": "2020-05-14 14:44:09.069921",
+ "modified": "2020-05-20 11:20:07.992597",
"modified_by": "Administrator",
"module": "HR",
"name": "Human Resource",
@@ -32,6 +32,9 @@
"step": "Create Employee"
},
{
+ "step": "Create Leave Type"
+ },
+ {
"step": "Create Leave Allocation"
},
{
@@ -41,7 +44,7 @@
"step": "HR Settings"
}
],
- "subtitle": "Employee, Recruitment, Payroll and more.",
+ "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
diff --git a/erpnext/hr/onboarding_step/create_department/create_department.json b/erpnext/hr/onboarding_step/create_department/create_department.json
index c3e4615..66a54cf 100644
--- a/erpnext/hr/onboarding_step/create_department/create_department.json
+++ b/erpnext/hr/onboarding_step/create_department/create_department.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
+ "is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 12:22:26.448420",
"modified_by": "Administrator",
"name": "Create Department",
"owner": "Administrator",
"reference_document": "Department",
- "title": "Create Department"
+ "show_full_form": 0,
+ "title": "Create Department",
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_designation/create_designation.json b/erpnext/hr/onboarding_step/create_designation/create_designation.json
index b58c06c..c4e9cc7 100644
--- a/erpnext/hr/onboarding_step/create_designation/create_designation.json
+++ b/erpnext/hr/onboarding_step/create_designation/create_designation.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
+ "is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 12:22:41.500795",
"modified_by": "Administrator",
"name": "Create Designation",
"owner": "Administrator",
"reference_document": "Designation",
- "title": "Create Designation"
+ "show_full_form": 0,
+ "title": "Create Designation",
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_employee/create_employee.json b/erpnext/hr/onboarding_step/create_employee/create_employee.json
index b4988a5..3aa33c6 100644
--- a/erpnext/hr/onboarding_step/create_employee/create_employee.json
+++ b/erpnext/hr/onboarding_step/create_employee/create_employee.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 1,
+ "is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 12:26:28.629074",
"modified_by": "Administrator",
"name": "Create Employee",
"owner": "Administrator",
"reference_document": "Employee",
- "title": "Create Employee"
+ "show_full_form": 0,
+ "title": "Create Employee",
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json
index 0db67d6..25cb9fe 100644
--- a/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json
+++ b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 1,
+ "is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 12:25:38.068582",
"modified_by": "Administrator",
"name": "Create Holiday list",
"owner": "Administrator",
"reference_document": "Holiday List",
- "title": "Create Holiday list"
+ "show_full_form": 0,
+ "title": "Create Holiday list",
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json b/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json
index 287dd6c..fa9941e 100644
--- a/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json
+++ b/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 1,
+ "is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 11:48:56.123718",
"modified_by": "Administrator",
"name": "Create Leave Allocation",
"owner": "Administrator",
"reference_document": "Leave Allocation",
- "title": "Create Leave Allocation"
+ "show_full_form": 0,
+ "title": "Create Leave Allocation",
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json b/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json
index 7b70a7f..1ed074e 100644
--- a/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json
+++ b/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 1,
+ "is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 11:49:45.400764",
"modified_by": "Administrator",
"name": "Create Leave Application",
"owner": "Administrator",
"reference_document": "Leave Application",
- "title": "Create Leave Application"
+ "show_full_form": 0,
+ "title": "Create Leave Application",
+ "validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json b/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json
new file mode 100644
index 0000000..e8b97c2
--- /dev/null
+++ b/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-20 11:17:31.119312",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-20 11:17:31.119312",
+ "modified_by": "Administrator",
+ "name": "Create Leave Type",
+ "owner": "Administrator",
+ "reference_document": "Leave Type",
+ "show_full_form": 0,
+ "title": "Create Leave Type",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/hr_settings/hr_settings.json b/erpnext/hr/onboarding_step/hr_settings/hr_settings.json
index 1f4f1bf..a8c96fb 100644
--- a/erpnext/hr/onboarding_step/hr_settings/hr_settings.json
+++ b/erpnext/hr/onboarding_step/hr_settings/hr_settings.json
@@ -6,11 +6,14 @@
"idx": 0,
"is_complete": 0,
"is_mandatory": 0,
+ "is_single": 1,
"is_skipped": 0,
- "modified": "2020-05-14 13:13:52.427711",
+ "modified": "2020-05-20 11:16:42.430974",
"modified_by": "Administrator",
"name": "HR Settings",
"owner": "Administrator",
"reference_document": "HR Settings",
- "title": "HR settings"
+ "show_full_form": 0,
+ "title": "HR settings",
+ "validate_action": 0
}
\ No newline at end of file
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 096e3c5..efc43bf 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
@@ -118,13 +118,8 @@
chart["type"] = "line"
- chart['lineOptions'] = {
- "dotSize": 6
- }
return chart
-
-
def add_data(employee_map, att_map, filters, holiday_map, conditions,leave_list=None):
record = []
diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
index f97e596..961c05c 100644
--- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
+++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
@@ -38,7 +38,7 @@
for pledge in self.securities:
if not pledge.qty and not pledge.amount:
- frappe.throw(_("Qty or Amount is mandatroy for loan security"))
+ frappe.throw(_("Qty or Amount is mandatory for loan security!"))
if not (self.loan_application and pledge.loan_security_price):
pledge.loan_security_price = get_loan_security_price(pledge.loan_security)
diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py
new file mode 100644
index 0000000..ef61f23
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_fixtures.py
@@ -0,0 +1,240 @@
+# 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 _
+from frappe.utils import nowdate, get_date_str
+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_dashboards():
+ return [{
+ "name": "Manufacturing",
+ "dashboard_name": "Manufacturing",
+ "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" }
+ ],
+ "cards": [
+ { "card": "Total Work Order" },
+ { "card": "Completed Work Order" },
+ { "card": "Ongoing Job Card" },
+ { "card": "Total Quality Inspection"}
+ ]
+ }]
+
+def get_charts():
+ company = erpnext.get_default_company()
+
+ if not company:
+ company = frappe.db.get_value("Company", {"is_group": 0}, "name")
+
+ return [{
+ "doctype": "Dashboard Chart",
+ "based_on": "modified",
+ "time_interval": "Yearly",
+ "chart_type": "Sum",
+ "chart_name": _("Produced Quantity"),
+ "name": "Produced Quantity",
+ "document_type": "Work Order",
+ "filters_json": json.dumps([['Work Order', 'docstatus', '=', 1, False]]),
+ "group_by_type": "Count",
+ "time_interval": "Monthly",
+ "timespan": "Last Year",
+ "owner": "Administrator",
+ "type": "Line",
+ "value_based_on": "produced_qty",
+ "is_public": 1,
+ "timeseries": 1
+ }, {
+ "doctype": "Dashboard Chart",
+ "based_on": "creation",
+ "time_interval": "Yearly",
+ "chart_type": "Sum",
+ "chart_name": _("Completed Operation"),
+ "name": "Completed Operation",
+ "document_type": "Work Order Operation",
+ "filters_json": json.dumps([['Work Order Operation', 'docstatus', '=', 1, False]]),
+ "group_by_type": "Count",
+ "time_interval": "Quarterly",
+ "timespan": "Last Year",
+ "owner": "Administrator",
+ "type": "Line",
+ "value_based_on": "completed_qty",
+ "is_public": 1,
+ "timeseries": 1
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Work Order Analysis"),
+ "name": "Work Order Analysis",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "owner": "Administrator",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Status"}),
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "axisOptions": {
+ "shortenYAxisNumbers": 1
+ },
+ "height": 300
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Quality Inspection Analysis"),
+ "name": "Quality Inspection Analysis",
+ "timespan": "Last Year",
+ "report_name": "Quality Inspection Summary",
+ "owner": "Administrator",
+ "filters_json": json.dumps({}),
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "axisOptions": {
+ "shortenYAxisNumbers": 1
+ },
+ "height": 300
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Pending Work Order"),
+ "name": "Pending Work Order",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Age"}),
+ "owner": "Administrator",
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "axisOptions": {
+ "shortenYAxisNumbers": 1
+ },
+ "height": 300
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Last Month Downtime Analysis"),
+ "name": "Last Month Downtime Analysis",
+ "timespan": "Last Year",
+ "filters_json": json.dumps({}),
+ "report_name": "Downtime Analysis",
+ "owner": "Administrator",
+ "is_public": 1,
+ "is_custom": 1,
+ "type": "Bar"
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Work Order Qty Analysis"),
+ "name": "Work Order Qty Analysis",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Quantity"}),
+ "owner": "Administrator",
+ "type": "Bar",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "barOptions": { "stacked": 1 }
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Job Card Analysis"),
+ "name": "Job Card Analysis",
+ "timespan": "Last Year",
+ "report_name": "Job Card Summary",
+ "owner": "Administrator",
+ "is_public": 1,
+ "is_custom": 1,
+ "filters_json": json.dumps({"company": company, "docstatus": 1, "range":"Monthly"}),
+ "custom_options": json.dumps({
+ "barOptions": { "stacked": 1 }
+ }),
+ "type": "Bar"
+ }]
+
+def get_number_cards():
+ fiscal_year = get_fiscal_year(date=nowdate())
+ year_start_date = get_date_str(fiscal_year[1])
+ year_end_date = get_date_str(fiscal_year[2])
+
+ return [{
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "name": "Total Work Order",
+ "filters_json": json.dumps([
+ ['Work Order', 'docstatus', '=', 1],
+ ['Work Order', 'creation', 'between', [year_start_date, year_end_date]]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Total Work Order"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "name": "Completed Work Order",
+ "filters_json": json.dumps([
+ ['Work Order', 'status', '=', 'Completed'],
+ ['Work Order', 'docstatus', '=', 1],
+ ['Work Order', 'creation', 'between', [year_start_date, year_end_date]]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Completed Work Order"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Job Card",
+ "name": "Ongoing Job Card",
+ "filters_json": json.dumps([
+ ['Job Card', 'status','!=','Completed'],
+ ['Job Card', 'docstatus', '=', 1]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Ongoing Job Card"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Quality Inspection",
+ "name": "Total Quality Inspection",
+ "filters_json": json.dumps([['Quality Inspection', 'docstatus', '=', 1]]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Total Quality Inspection"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ }]
\ No newline at end of file
diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
index 18604e2..ecd2dc9 100644
--- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
@@ -3,7 +3,7 @@
{
"hidden": 0,
"label": "Production",
- "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Activity Type\"\n ],\n \"description\": \"Time Sheet for manufacturing.\",\n \"label\": \"Timesheet\",\n \"name\": \"Timesheet\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n }\n]"
+ "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Downtime Entry\",\n \"name\": \"Downtime Entry\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@@ -13,7 +13,7 @@
{
"hidden": 0,
"label": "Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Open Work Orders\",\n \"name\": \"Open Work Orders\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Work Orders in Progress\",\n \"name\": \"Work Orders in Progress\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Issued Items Against Work Order\",\n \"name\": \"Issued Items Against Work Order\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Completed Work Orders\",\n \"name\": \"Completed Work Orders\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Production Analytics\",\n \"name\": \"Production Analytics\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Search\",\n \"name\": \"BOM Search\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Stock Report\",\n \"name\": \"BOM Stock Report\",\n \"type\": \"report\"\n }\n]"
+ "links": "[{\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Planning Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Planning Report\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Work Order Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Work Order Summary\"\n}, {\n\t\"dependencies\": [\"Quality Inspection\"],\n\t\"name\": \"Quality Inspection Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Quality Inspection\",\n\t\"label\": \"Quality Inspection Summary\"\n}, {\n\t\"dependencies\": [\"Downtime Entry\"],\n\t\"name\": \"Downtime Analysis\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Downtime Entry\",\n\t\"label\": \"Downtime Analysis\"\n}, {\n\t\"dependencies\": [\"Job Card\"],\n\t\"name\": \"Job Card Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Job Card\",\n\t\"label\": \"Job Card Summary\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Search\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Search\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Stock Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Stock Report\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Analytics\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Analytics\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Operations Time\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Operations Time\"\n}]"
},
{
"hidden": 0,
@@ -32,7 +32,11 @@
}
],
"category": "Domains",
- "charts": [],
+ "charts": [
+ {
+ "chart_name": "Produced Quantity"
+ }
+ ],
"creation": "2020-03-02 17:11:37.032604",
"developer_mode_only": 0,
"disable_user_customization": 0,
@@ -42,13 +46,59 @@
"idx": 0,
"is_standard": 1,
"label": "Manufacturing",
- "modified": "2020-04-01 11:28:50.979358",
+ "modified": "2020-05-19 14:05:59.100891",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing",
+ "onboarding": "Manufacturing",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"restrict_to_domain": "Manufacturing",
- "shortcuts": []
+ "shortcuts": [
+ {
+ "format": "{} Active",
+ "label": "Item",
+ "link_to": "Item",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{\n \"disabled\": 0\n}",
+ "type": "DocType"
+ },
+ {
+ "format": "{} Active",
+ "label": "BOM",
+ "link_to": "BOM",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{\n \"is_active\": 1\n}",
+ "type": "DocType"
+ },
+ {
+ "format": "{} Open",
+ "label": "Work Order",
+ "link_to": "Work Order",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{ \n \"status\": [\"in\", \n [\"Draft\", \"Not Started\", \"In Process\"]\n ]\n}",
+ "type": "DocType"
+ },
+ {
+ "format": "{} Open",
+ "label": "Production Plan",
+ "link_to": "Production Plan",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{ \n \"status\": [\"not in\", [\"Completed\"]]\n}",
+ "type": "DocType"
+ },
+ {
+ "label": "Work Order Summary",
+ "link_to": "Work Order Summary",
+ "restrict_to_domain": "Manufacturing",
+ "type": "Report"
+ },
+ {
+ "label": "Dashboard",
+ "link_to": "Manufacturing",
+ "restrict_to_domain": "Manufacturing",
+ "type": "Dashboard"
+ }
+ ]
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index ebfb762..44da9ca 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -212,6 +212,12 @@
});
},
+ rm_cost_as_per: function(frm) {
+ if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) {
+ frm.set_value("plc_conversion_rate", 1.0);
+ }
+ },
+
routing: function(frm) {
if (frm.doc.routing) {
frappe.call({
@@ -242,7 +248,7 @@
item_code: function(doc, cdt, cdn){
var scrap_items = false;
var child = locals[cdt][cdn];
- if(child.doctype == 'BOM Scrap Item') {
+ if (child.doctype == 'BOM Scrap Item') {
scrap_items = true;
}
@@ -252,8 +258,19 @@
get_bom_material_detail(doc, cdt, cdn, scrap_items);
},
+
+ buying_price_list: function(doc) {
+ this.apply_price_list();
+ },
+
+ plc_conversion_rate: function(doc) {
+ if (!this.in_apply_price_list) {
+ this.apply_price_list();
+ }
+ },
+
conversion_factor: function(doc, cdt, cdn) {
- if(frappe.meta.get_docfield(cdt, "stock_qty", cdn)) {
+ if (frappe.meta.get_docfield(cdt, "stock_qty", cdn)) {
var item = frappe.get_doc(cdt, cdn);
frappe.model.round_floats_in(item, ["qty", "conversion_factor"]);
item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item));
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 63f4f97..4ce0ecf 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"creation": "2013-01-22 15:11:38",
"doctype": "DocType",
@@ -6,23 +7,25 @@
"engine": "InnoDB",
"field_order": [
"item",
- "quantity",
- "set_rate_of_sub_assembly_item_based_on_bom",
+ "company",
+ "item_name",
+ "uom",
"cb0",
"is_active",
"is_default",
"allow_alternative_item",
- "image",
- "item_name",
- "uom",
- "currency_detail",
- "company",
+ "set_rate_of_sub_assembly_item_based_on_bom",
"project",
+ "quantity",
+ "image",
+ "currency_detail",
+ "currency",
"conversion_rate",
"column_break_12",
- "currency",
"rm_cost_as_per",
"buying_price_list",
+ "price_list_currency",
+ "plc_conversion_rate",
"section_break_21",
"with_operations",
"column_break_23",
@@ -176,7 +179,8 @@
},
{
"fieldname": "currency_detail",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Currency and Price List"
},
{
"fieldname": "company",
@@ -324,7 +328,7 @@
},
{
"fieldname": "base_scrap_material_cost",
- "fieldtype": "Data",
+ "fieldtype": "Currency",
"label": "Scrap Material Cost(Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
@@ -477,13 +481,31 @@
{
"fieldname": "column_break_52",
"fieldtype": "Column Break"
+ },
+ {
+ "allow_on_submit": 1,
+ "depends_on": "eval:doc.rm_cost_as_per=='Price List'",
+ "fieldname": "plc_conversion_rate",
+ "fieldtype": "Float",
+ "label": "Price List Exchange Rate"
+ },
+ {
+ "allow_on_submit": 1,
+ "depends_on": "eval:doc.rm_cost_as_per=='Price List'",
+ "fieldname": "price_list_currency",
+ "fieldtype": "Link",
+ "label": "Price List Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-sitemap",
"idx": 1,
"image_field": "image",
"is_submittable": 1,
- "modified": "2019-11-22 14:35:12.142150",
+ "links": [],
+ "modified": "2020-05-05 14:29:32.634952",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index b1fc4de..6ac653e 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -70,11 +70,13 @@
self.validate_main_item()
self.validate_currency()
self.set_conversion_rate()
+ self.set_plc_conversion_rate()
self.validate_uom_is_interger()
self.set_bom_material_details()
self.validate_materials()
self.validate_operations()
self.calculate_cost()
+ self.update_cost(update_parent=False, from_child_bom=True, save=False)
def get_context(self, context):
context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
@@ -165,7 +167,7 @@
'rate' : rate,
'qty' : args.get("qty") or args.get("stock_qty") or 1,
'stock_qty' : args.get("qty") or args.get("stock_qty") or 1,
- 'base_rate' : rate,
+ 'base_rate' : flt(rate) * (flt(self.conversion_rate) or 1),
'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0
}
@@ -226,7 +228,7 @@
frappe.msgprint(_("{0} not found for item {1}")
.format(self.rm_cost_as_per, arg["item_code"]), alert=True)
- return flt(rate) / (self.conversion_rate or 1)
+ return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1)
def update_cost(self, update_parent=True, from_child_bom=False, save=True):
if self.docstatus == 2:
@@ -243,10 +245,15 @@
"stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor
})
+
if rate:
d.rate = rate
d.amount = flt(d.rate) * flt(d.qty)
- d.db_update()
+ d.base_rate = flt(d.rate) * flt(self.conversion_rate)
+ d.base_amount = flt(d.amount) * flt(self.conversion_rate)
+
+ if save:
+ d.db_update()
if self.docstatus == 1:
self.flags.ignore_validate_update_after_submit = True
@@ -372,6 +379,13 @@
elif self.conversion_rate == 1 or flt(self.conversion_rate) <= 0:
self.conversion_rate = get_exchange_rate(self.currency, self.company_currency(), args="for_buying")
+ def set_plc_conversion_rate(self):
+ if self.rm_cost_as_per in ["Valuation Rate", "Last Purchase Rate"]:
+ self.plc_conversion_rate = 1
+ elif not self.plc_conversion_rate and self.price_list_currency:
+ self.plc_conversion_rate = get_exchange_rate(self.price_list_currency,
+ self.company_currency(), args="for_buying")
+
def validate_materials(self):
""" Validate raw material entries """
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 45a7b93..3dfd03b 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -81,13 +81,13 @@
# test amounts in selected currency
self.assertEqual(bom.operating_cost, 100)
- self.assertEqual(bom.raw_material_cost, 8000)
- self.assertEqual(bom.total_cost, 8100)
+ self.assertEqual(bom.raw_material_cost, 351.68)
+ self.assertEqual(bom.total_cost, 451.68)
# test amounts in selected currency
self.assertEqual(bom.base_operating_cost, 6000)
- self.assertEqual(bom.base_raw_material_cost, 480000)
- self.assertEqual(bom.base_total_cost, 486000)
+ self.assertEqual(bom.base_raw_material_cost, 21100.80)
+ self.assertEqual(bom.base_total_cost, 27100.80)
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
diff --git a/erpnext/manufacturing/doctype/downtime_entry/__init__.py b/erpnext/manufacturing/doctype/downtime_entry/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/__init__.py
diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js
new file mode 100644
index 0000000..3b7f5ba
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.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('Downtime Entry', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json
new file mode 100644
index 0000000..9acb4f0
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json
@@ -0,0 +1,132 @@
+{
+ "actions": [],
+ "allow_import": 1,
+ "creation": "2020-04-18 04:50:46.187638",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "workstation",
+ "operator",
+ "column_break_4",
+ "from_time",
+ "to_time",
+ "downtime",
+ "downtime_reason_section",
+ "stop_reason",
+ "column_break_9",
+ "remarks"
+ ],
+ "fields": [
+ {
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "label": "Workstation / Machine",
+ "options": "Workstation",
+ "reqd": 1
+ },
+ {
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "From Time",
+ "reqd": 1
+ },
+ {
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "To Time",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "operator",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Operator",
+ "options": "Employee",
+ "reqd": 1
+ },
+ {
+ "fieldname": "downtime_reason_section",
+ "fieldtype": "Section Break",
+ "label": "Downtime Reason"
+ },
+ {
+ "description": "In Mins",
+ "fieldname": "downtime",
+ "fieldtype": "Float",
+ "label": "Downtime",
+ "read_only": 1
+ },
+ {
+ "fieldname": "stop_reason",
+ "fieldtype": "Select",
+ "label": "Stop Reason",
+ "options": "\nExcessive machine set up time\nUnplanned machine maintenance\nOn-machine press checks\nMachine operator errors\nMachine malfunction\nElectricity down\nOther",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "remarks",
+ "fieldtype": "Text",
+ "label": "Remarks"
+ }
+ ],
+ "links": [],
+ "modified": "2020-05-19 12:59:37.358483",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Downtime Entry",
+ "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": "Manufacturing User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "workstation",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py
new file mode 100644
index 0000000..56ec435
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py
@@ -0,0 +1,13 @@
+# -*- 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.utils import time_diff_in_hours
+from frappe.model.document import Document
+
+class DowntimeEntry(Document):
+ def validate(self):
+ if self.from_time and self.to_time:
+ self.downtime = time_diff_in_hours(self.to_time, self.from_time) * 60
diff --git a/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py
new file mode 100644
index 0000000..8b2a8d3
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.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 TestDowntimeEntry(unittest.TestCase):
+ pass
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 7661fff..fba670c 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "naming_series:",
"creation": "2018-07-09 17:23:29.518745",
"doctype": "DocType",
@@ -264,8 +265,10 @@
{
"fetch_from": "work_order.production_item",
"fieldname": "production_item",
- "fieldtype": "Read Only",
- "label": "Production Item"
+ "fieldtype": "Link",
+ "label": "Production Item",
+ "options": "Item",
+ "read_only": 1
},
{
"fieldname": "barcode",
@@ -274,7 +277,8 @@
"read_only": 1
},
{
- "fetch_from": "work_order.item_name",
+ "fetch_from": "production_item.item_name",
+ "fetch_if_empty": 1,
"fieldname": "item_name",
"fieldtype": "Read Only",
"label": "Item Name"
@@ -290,7 +294,8 @@
}
],
"is_submittable": 1,
- "modified": "2020-03-27 13:36:35.417502",
+ "links": [],
+ "modified": "2020-04-20 15:14:00.273441",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 00a67a0..585a09d 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -38,11 +38,12 @@
"required_items",
"time",
"planned_start_date",
- "actual_start_date",
- "column_break_13",
"planned_end_date",
- "actual_end_date",
"expected_delivery_date",
+ "column_break_13",
+ "actual_start_date",
+ "actual_end_date",
+ "lead_time",
"operations_section",
"transfer_material_against",
"operations",
@@ -108,6 +109,8 @@
},
{
"depends_on": "eval:doc.production_item",
+ "fetch_from": "production_item.item_name",
+ "fetch_if_empty": 1,
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
@@ -281,27 +284,30 @@
"reqd": 1
},
{
+ "allow_on_submit": 1,
"fieldname": "actual_start_date",
"fieldtype": "Datetime",
"label": "Actual Start Date",
- "read_only": 1
+ "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0"
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
+ "allow_on_submit": 1,
"fieldname": "planned_end_date",
"fieldtype": "Datetime",
"label": "Planned End Date",
"no_copy": 1,
- "read_only": 1
+ "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0"
},
{
+ "allow_on_submit": 1,
"fieldname": "actual_end_date",
"fieldtype": "Datetime",
"label": "Actual End Date",
- "read_only": 1
+ "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0"
},
{
"allow_on_submit": 1,
@@ -476,6 +482,13 @@
"fieldtype": "Link",
"label": "Source Warehouse",
"options": "Warehouse"
+ },
+ {
+ "description": "In Mins",
+ "fieldname": "lead_time",
+ "fieldtype": "Float",
+ "label": "Lead Time",
+ "read_only": 1
}
],
"icon": "fa fa-cogs",
@@ -483,7 +496,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2020-04-24 19:32:43.323054",
+ "modified": "2020-05-05 19:32:43.323054",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 8301f30..c278955 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -6,7 +6,7 @@
import json
import math
from frappe import _
-from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form
+from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form, time_diff_in_hours
from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict
from dateutil.relativedelta import relativedelta
@@ -279,7 +279,7 @@
if enable_capacity_planning and job_card_doc:
row.planned_start_time = job_card_doc.time_logs[-1].from_time
row.planned_end_time = job_card_doc.time_logs[-1].to_time
- print(row.planned_start_time, original_start_time, plan_days)
+
if date_diff(row.planned_start_time, original_start_time) > plan_days:
frappe.message_log.pop()
frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.")
@@ -437,8 +437,6 @@
frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'"))
def set_actual_dates(self):
- self.actual_start_date = None
- self.actual_end_date = None
if self.get("operations"):
actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time]
if actual_start_dates:
@@ -447,6 +445,27 @@
actual_end_dates = [d.actual_end_time for d in self.get("operations") if d.actual_end_time]
if actual_end_dates:
self.actual_end_date = max(actual_end_dates)
+ else:
+ data = frappe.get_all("Stock Entry",
+ fields = ["timestamp(posting_date, posting_time) as posting_datetime"],
+ filters = {
+ "work_order": self.name,
+ "purpose": ("in", ["Material Transfer for Manufacture", "Manufacture"])
+ }
+ )
+
+ if data and len(data):
+ dates = [d.posting_datetime for d in data]
+ self.actual_start_date = min(dates)
+
+ if self.status == "Completed":
+ self.actual_end_date = max(dates)
+
+ self.set_lead_time()
+
+ def set_lead_time(self):
+ if self.actual_start_date and self.actual_end_date:
+ self.lead_time = flt(time_diff_in_hours(self.actual_end_date, self.actual_start_date) * 60)
def delete_job_card(self):
for d in frappe.get_all("Job Card", ["name"], {"work_order": self.name}):
diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
new file mode 100644
index 0000000..952d1f0
--- /dev/null
+++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
@@ -0,0 +1,57 @@
+{
+ "allow_roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Item Manager"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-05 16:37:08.238935",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-19 12:51:42.744570",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Manufacturing",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Warehouse"
+ },
+ {
+ "step": "Workstation"
+ },
+ {
+ "step": "Operation"
+ },
+ {
+ "step": "Create Product"
+ },
+ {
+ "step": "Create Raw Materials"
+ },
+ {
+ "step": "Create BOM"
+ },
+ {
+ "step": "Work Order"
+ },
+ {
+ "step": "Explore Manufacturing Settings"
+ }
+ ],
+ "subtitle": "Products, Raw Materials, BOM, Work Order and more.",
+ "success_message": "Manufacturing module is all setup!",
+ "title": "Let's Setup Manufacturing Module",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json
new file mode 100644
index 0000000..50584e1
--- /dev/null
+++ b/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json
@@ -0,0 +1,54 @@
+{
+ "allow_roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Item Manager"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-05 16:37:08.238935",
+ "docstatus": 0,
+ "doctype": "Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-12 16:22:07.050224",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Manufacturing",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Introduction to Manufacturing"
+ },
+ {
+ "step": "Warehouse"
+ },
+ {
+ "step": "Workstation"
+ },
+ {
+ "step": "Operation"
+ },
+ {
+ "step": "Create Product"
+ },
+ {
+ "step": "Create BOM"
+ },
+ {
+ "step": "Work Order"
+ }
+ ],
+ "subtitle": "Products, Raw Materials, BOM, Work Order and more.",
+ "success_message": "Manufacturing module is all setup!",
+ "title": "Let's Setup Manufacturing Module",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
new file mode 100644
index 0000000..84b4088
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-05 16:41:20.239696",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:51:31.315686",
+ "modified_by": "Administrator",
+ "name": "Create BOM",
+ "owner": "Administrator",
+ "reference_document": "BOM",
+ "show_full_form": 1,
+ "title": "Create a BOM (Bill of Material)",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_product/create_product.json b/erpnext/manufacturing/onboarding_step/create_product/create_product.json
new file mode 100644
index 0000000..0ffa301
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/create_product/create_product.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-05 16:42:31.476275",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:59.010439",
+ "modified_by": "Administrator",
+ "name": "Create Product",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create a Finished Good",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
new file mode 100644
index 0000000..0764f2e
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 11:53:17.295372",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 11:53:25.147837",
+ "modified_by": "Administrator",
+ "name": "Create Raw Materials",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create Raw Materials",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
new file mode 100644
index 0000000..582aba4
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
@@ -0,0 +1,20 @@
+{
+ "action": "Update Settings",
+ "creation": "2020-05-19 11:55:11.378374",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:12:28.145366",
+ "modified_by": "Administrator",
+ "name": "Explore Manufacturing Settings",
+ "owner": "Administrator",
+ "reference_document": "Manufacturing Settings",
+ "show_full_form": 0,
+ "title": "Explore Manufacturing Settings",
+ "validate_action": 0,
+ "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json b/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json
new file mode 100644
index 0000000..eb7ab3a
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json
@@ -0,0 +1,20 @@
+{
+ "action": "Update Settings",
+ "creation": "2020-05-05 16:40:23.676406",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 19:11:57.152883",
+ "modified_by": "Administrator",
+ "name": "Introduction to Manufacturing",
+ "owner": "Administrator",
+ "reference_document": "Manufacturing Settings",
+ "show_full_form": 0,
+ "title": "Manufacturing Settings",
+ "validate_action": 1,
+ "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/operation/operation.json b/erpnext/manufacturing/onboarding_step/operation/operation.json
new file mode 100644
index 0000000..b532e67
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/operation/operation.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:15:31.706756",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:41.642754",
+ "modified_by": "Administrator",
+ "name": "Operation",
+ "owner": "Administrator",
+ "reference_document": "Operation",
+ "show_full_form": 0,
+ "title": "Create a Operation",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json
new file mode 100644
index 0000000..e23bd33
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:13:34.014554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:13.766712",
+ "modified_by": "Administrator",
+ "name": "Warehouse",
+ "owner": "Administrator",
+ "reference_document": "Warehouse",
+ "show_full_form": 0,
+ "title": "Create a Warehouse",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/work_order/work_order.json b/erpnext/manufacturing/onboarding_step/work_order/work_order.json
new file mode 100644
index 0000000..c63363e
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/work_order/work_order.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:15:56.084682",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:51:38.133150",
+ "modified_by": "Administrator",
+ "name": "Work Order",
+ "owner": "Administrator",
+ "reference_document": "Work Order",
+ "show_full_form": 1,
+ "title": "Create a Work Order",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/workstation/workstation.json b/erpnext/manufacturing/onboarding_step/workstation/workstation.json
new file mode 100644
index 0000000..df244bb
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/workstation/workstation.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:14:14.930214",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:33.938176",
+ "modified_by": "Administrator",
+ "name": "Workstation",
+ "owner": "Administrator",
+ "reference_document": "Workstation",
+ "show_full_form": 0,
+ "title": "Create a Workstation / Machine",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/bom_operations_time/__init__.py b/erpnext/manufacturing/report/bom_operations_time/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/__init__.py
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
new file mode 100644
index 0000000..7468e34
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
@@ -0,0 +1,9 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["BOM Operations Time"] = {
+ "filters": [
+
+ ]
+};
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
new file mode 100644
index 0000000..665c5b9
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
@@ -0,0 +1,28 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-03-03 01:41:20.862521",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "",
+ "modified": "2020-03-03 01:41:20.862521",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "BOM Operations Time",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "BOM",
+ "report_name": "BOM Operations Time",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Manufacturing User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
new file mode 100644
index 0000000..1279011
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
@@ -0,0 +1,112 @@
+# 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):
+ data = get_data(filters)
+ columns = get_columns(filters)
+ return columns, data
+
+def get_data(filters):
+ data = []
+
+ bom_data = []
+ for d in frappe.db.sql("""
+ SELECT
+ bom.name, bom.item, bom.item_name, bom.uom,
+ bomps.operation, bomps.workstation, bomps.time_in_mins
+ FROM `tabBOM` bom, `tabBOM Operation` bomps
+ WHERE
+ bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent
+ """, as_dict=1):
+ row = get_args()
+ if d.name not in bom_data:
+ bom_data.append(d.name)
+ row.update(d)
+ else:
+ row.update({
+ "operation": d.operation,
+ "workstation": d.workstation,
+ "time_in_mins": d.time_in_mins
+ })
+
+ data.append(row)
+
+ used_as_subassembly_items = get_bom_count(bom_data)
+
+ for d in data:
+ d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0)
+
+ return data
+
+def get_bom_count(bom_data):
+ data = frappe.get_all("BOM Item",
+ fields=["count(name) as count", "bom_no"],
+ filters= {"bom_no": ("in", bom_data)}, group_by = "bom_no")
+
+ bom_count = {}
+ for d in data:
+ bom_count.setdefault(d.bom_no, d.count)
+
+ return bom_count
+
+def get_args():
+ return frappe._dict({
+ "name": "",
+ "item": "",
+ "item_name": "",
+ "uom": ""
+ })
+
+def get_columns(filters):
+ return [{
+ "label": _("BOM ID"),
+ "options": "BOM",
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "width": 140
+ }, {
+ "label": _("BOM Item Code"),
+ "options": "Item",
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "width": 140
+ }, {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 110
+ }, {
+ "label": _("UOM"),
+ "options": "UOM",
+ "fieldname": "uom",
+ "fieldtype": "Link",
+ "width": 140
+ }, {
+ "label": _("Operation"),
+ "options": "Operation",
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "width": 120
+ }, {
+ "label": _("Workstation"),
+ "options": "Workstation",
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "width": 110
+ }, {
+ "label": _("Time (In Mins)"),
+ "fieldname": "time_in_mins",
+ "fieldtype": "Int",
+ "width": 140
+ }, {
+ "label": _("Subassembly BOM Count"),
+ "fieldname": "used_as_subassembly_items",
+ "fieldtype": "Int",
+ "width": 180
+ }]
+
+
diff --git a/erpnext/manufacturing/report/downtime_analysis/__init__.py b/erpnext/manufacturing/report/downtime_analysis/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/__init__.py
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js
new file mode 100644
index 0000000..ff32dbe
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js
@@ -0,0 +1,28 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Downtime Analysis"] = {
+ "filters": [
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Datetime",
+ default: frappe.datetime.add_months(frappe.datetime.now_datetime(), -1),
+ reqd: 1
+ },
+ {
+ label: __("To Date"),
+ fieldname:"to_date",
+ fieldtype: "Datetime",
+ default: frappe.datetime.now_datetime(),
+ reqd: 1,
+ },
+ {
+ label: __("Machine"),
+ fieldname: "workstation",
+ fieldtype: "Link",
+ options: "Workstation"
+ }
+ ]
+};
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json
new file mode 100644
index 0000000..5edc778
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 1,
+ "creation": "2020-04-20 18:26:04.345289",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-20 18:26:04.345289",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Downtime Analysis",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Downtime Entry",
+ "report_name": "Downtime Analysis",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "System Manager"
+ },
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
new file mode 100644
index 0000000..2b2be4f
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
@@ -0,0 +1,108 @@
+# 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.utils import flt
+from frappe import _
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data, None, chart_data
+
+def get_data(filters):
+ query_filters = {}
+
+ fields = ["name", "workstation", "operator", "from_time", "to_time", "downtime", "stop_reason", "remarks"]
+
+ query_filters["from_time"] = (">=", filters.get("from_date"))
+ query_filters["to_time"] = ("<=", filters.get("to_date"))
+
+ if filters.get("workstation"):
+ query_filters["workstation"] = filters.get("workstation")
+
+ return frappe.get_all("Downtime Entry", fields= fields, filters=query_filters)
+
+def get_chart_data(data, columns):
+ labels = sorted(list(set([d.workstation for d in data])))
+
+ workstation_wise_data = {}
+ for d in data:
+ if d.workstation not in workstation_wise_data:
+ workstation_wise_data[d.workstation] = 0
+
+ workstation_wise_data[d.workstation] += flt(d.downtime, 2)
+
+ datasets = []
+ for label in labels:
+ datasets.append(workstation_wise_data.get(label, 0))
+
+ chart = {
+ "data": {
+ "labels": labels,
+ "datasets": [
+ {"name": "Dataset 1", "values": datasets}
+ ]
+ },
+ "type": "bar"
+ }
+
+ return chart
+
+def get_columns(filters):
+ return [
+ {
+ "label": _("ID"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Downtime Entry",
+ "width": 100
+ },
+ {
+ "label": _("Machine"),
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "options": "Workstation",
+ "width": 100
+ },
+ {
+ "label": _("Operator"),
+ "fieldname": "operator",
+ "fieldtype": "Link",
+ "options": "Employee",
+ "width": 130
+ },
+ {
+ "label": _("From Time"),
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "width": 160
+ },
+ {
+ "label": _("To Time"),
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "width": 160
+ },
+ {
+ "label": _("Downtime (In Mins)"),
+ "fieldname": "downtime",
+ "fieldtype": "Float",
+ "width": 150
+ },
+ {
+ "label": _("Stop Reason"),
+ "fieldname": "stop_reason",
+ "fieldtype": "Data",
+ "width": 220
+ },
+ {
+ "label": _("Remarks"),
+ "fieldname": "remarks",
+ "fieldtype": "Text",
+ "width": 100
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/job_card_summary/__init__.py b/erpnext/manufacturing/report/job_card_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/__init__.py
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
new file mode 100644
index 0000000..b7e3071
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
@@ -0,0 +1,52 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Job Card Summary"] = {
+ "filters": [
+ {
+ label: __("Company"),
+ fieldname: "company",
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -12),
+ reqd: 1
+ },
+ {
+ label: __("To Date"),
+ fieldname:"to_date",
+ fieldtype: "Date",
+ default: frappe.datetime.get_today(),
+ reqd: 1,
+ },
+ {
+ label: __("Status"),
+ fieldname: "status",
+ fieldtype: "Select",
+ options: ["", "Open", "Work In Progress", "Completed", "On Hold"]
+ },
+ {
+ label: __("Sales Orders"),
+ fieldname: "sales_order",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Sales Order', txt);
+ }
+ },
+ {
+ label: __("Production Item"),
+ fieldname: "production_item",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Item', txt);
+ }
+ }
+ ]
+};
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json
new file mode 100644
index 0000000..9f08fc3
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json
@@ -0,0 +1,34 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-04-20 12:00:21.436619",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-20 12:00:21.436619",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Job Card",
+ "report_name": "Job Card Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Stock Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
new file mode 100644
index 0000000..ae1e4f3
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
@@ -0,0 +1,186 @@
+# 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 _
+from frappe.utils import getdate, flt
+from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period)
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data, None, chart_data
+
+def get_data(filters):
+ query_filters = {"docstatus": ("<", 2)}
+
+ fields = ["name", "status", "work_order", "production_item", "item_name",
+ "total_completed_qty", "workstation", "operation", "employee_name", "total_time_in_mins"]
+
+ for field in ["work_order", "workstation", "operation", "company"]:
+ if filters.get(field):
+ query_filters[field] = ("in", filters.get(field))
+
+ data = frappe.get_all("Job Card",
+ fields= fields, filters=query_filters)
+
+ if not data: return []
+
+ job_cards = [d.name for d in data]
+ job_card_time_details = {}
+ for job_card_data in frappe.get_all("Job Card Time Log",
+ fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"],
+ filters={"docstatus": ("<", 2), "parent": ("in", job_cards)}, group_by="parent"):
+ job_card_time_details[job_card_data.parent] = job_card_data
+
+ for d in data:
+ if d.status == "Material Transferred":
+ d.status = "Open"
+
+ if job_card_time_details.get(d.name):
+ d.from_time = job_card_time_details.get(d.name).from_time
+ d.to_time = job_card_time_details.get(d.name).to_time
+
+ return data
+
+def get_chart_data(job_card_details, filters):
+ labels, periodic_data = prepare_chart_data(job_card_details, filters)
+
+ pending, completed = [], []
+ datasets = []
+
+ for d in labels:
+ pending.append(periodic_data.get("Pending").get(d))
+ completed.append(periodic_data.get("Completed").get(d))
+
+ datasets.append({"name": "Pending", "values": pending})
+ datasets.append({"name": "Completed", "values": completed})
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ "type": "bar"
+ }
+
+ return chart
+
+def prepare_chart_data(job_card_details, filters):
+ labels = []
+
+ periodic_data = {
+ "Pending": {},
+ "Completed": {}
+ }
+
+ filters.range = "Monthly"
+
+ ranges = get_period_date_ranges(filters)
+ for from_date, end_date in ranges:
+ period = get_period(end_date, filters)
+ if period not in labels:
+ labels.append(period)
+
+ for d in job_card_details:
+ if getdate(d.from_time) >= from_date and getdate(d.to_time) <= end_date:
+ status = "Completed" if d.status == "Completed" else "Pending"
+
+ if periodic_data.get(status) and periodic_data.get(status).get(period):
+ periodic_data[status][period] += 1
+ else:
+ periodic_data[status][period] = 1
+
+ return labels, periodic_data
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Id"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Job Card",
+ "width": 100
+ },
+ ]
+
+ if not filters.get("status"):
+ columns.append(
+ {
+ "label": _("Status"),
+ "fieldname": "status",
+ "width": 100
+ },
+ )
+
+ columns.extend([
+ {
+ "label": _("Work Order"),
+ "fieldname": "work_order",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100
+ },
+ {
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 110
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": _("Workstation"),
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "options": "Workstation",
+ "width": 110
+ },
+ {
+ "label": _("Operation"),
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "options": "Operation",
+ "width": 110
+ },
+ {
+ "label": _("Employee Name"),
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "width": 110
+ },
+ {
+ "label": _("Total Completed Qty"),
+ "fieldname": "total_completed_qty",
+ "fieldtype": "Float",
+ "width": 120
+ },
+ {
+ "label": _("From Time"),
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "width": 120
+ },
+ {
+ "label": _("To Time"),
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "width": 120
+ },
+ {
+ "label": _("Time Required (In Mins)"),
+ "fieldname": "total_time_in_mins",
+ "fieldtype": "Float",
+ "width": 100
+ }
+ ])
+
+ return columns
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py
index 145e4a7..79af8a1 100644
--- a/erpnext/manufacturing/report/production_analytics/production_analytics.py
+++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py
@@ -55,10 +55,8 @@
if d.status == 'Completed':
if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Completed", period)
-
elif getdate(d.actual_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Pending", period)
-
elif getdate(d.planned_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
else:
@@ -67,19 +65,17 @@
elif d.status == 'In Process':
if getdate(d.actual_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Pending", period)
-
elif getdate(d.planned_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
-
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
elif d.status == 'Not Started':
if getdate(d.planned_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
-
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
+
return periodic_data
def update_periodic_data(periodic_data, status, period):
@@ -147,4 +143,3 @@
-
diff --git a/erpnext/manufacturing/report/production_planning_report/__init__.py b/erpnext/manufacturing/report/production_planning_report/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/__init__.py
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.js b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js
new file mode 100644
index 0000000..675b8a1
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js
@@ -0,0 +1,111 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Production Planning Report"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_user_default("Company")
+ },
+ {
+ "fieldname":"based_on",
+ "label": __("Based On"),
+ "fieldtype": "Select",
+ "options": ["Sales Order", "Material Request", "Work Order"],
+ "default": "Sales Order",
+ "reqd": 1,
+ on_change: function() {
+ let filters = frappe.query_report.filters;
+ let based_on = frappe.query_report.get_filter_value('based_on');
+ let options = {
+ "Sales Order": ["Delivery Date", "Total Amount"],
+ "Material Request": ["Required Date"],
+ "Work Order": ["Planned Start Date"]
+ }
+
+ filters.forEach(d => {
+ if (d.fieldname == "order_by") {
+ d.df.options = options[based_on];
+ d.set_input(d.df.options)
+ }
+ });
+
+ frappe.query_report.refresh();
+ }
+ },
+ {
+ "fieldname":"docnames",
+ "label": __("Document Name"),
+ "fieldtype": "MultiSelectList",
+ "options": "Sales Order",
+ "get_data": function(txt) {
+ if (!frappe.query_report.filters) return;
+
+ let based_on = frappe.query_report.get_filter_value('based_on');
+ if (!based_on) return;
+
+ return frappe.db.get_link_options(based_on, txt);
+ },
+ "get_query": function() {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: {
+ "docstatus": 1,
+ "company": company
+ }
+ };
+ }
+ },
+ {
+ "fieldname":"raw_material_warehouse",
+ "label": __("Raw Material Warehouse"),
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "depends_on": "eval: doc.based_on != 'Work Order'",
+ "get_query": function() {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: {
+ "company": company
+ }
+ };
+ }
+ },
+ {
+ "fieldname":"order_by",
+ "label": __("Order By"),
+ "fieldtype": "Select",
+ "options": ["Delivery Date", "Total Amount"],
+ "default": "Delivery Date"
+ },
+ {
+ "fieldname":"include_subassembly_raw_materials",
+ "label": __("Include Sub-assembly Raw Materials"),
+ "fieldtype": "Check",
+ "depends_on": "eval: doc.based_on != 'Work Order'",
+ "default": 0
+ },
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "production_item_name" && data && data.qty_to_manufacture > data.available_qty ) {
+ value = `<div style="color:red">${value}</div>`;
+ }
+
+ if (column.fieldname == "production_item" && !data.name ) {
+ value = "";
+ }
+
+ if (column.fieldname == "raw_material_name" && data && data.required_qty > data.allotted_qty ) {
+ value = `<div style="color:red">${value}</div>`;
+ }
+
+ return value;
+ },
+};
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.json b/erpnext/manufacturing/report/production_planning_report/production_planning_report.json
new file mode 100644
index 0000000..f37dad3
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-03-06 11:37:43.180095",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "",
+ "modified": "2020-03-06 11:38:05.789851",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Planning Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Order",
+ "report_name": "Production Planning Report",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
new file mode 100644
index 0000000..b5e6c6f
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -0,0 +1,371 @@
+# 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 _
+from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
+
+# and bom_no is not null and bom_no !=''
+
+mapper = {
+ "Sales Order": {
+ "fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
+ stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse,
+ `tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """,
+ "filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty
+ and `tabSales Order`.per_delivered < 100.0"""
+ },
+ "Material Request": {
+ "fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
+ stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse,
+ `tabMaterial Request Item`.schedule_date """,
+ "filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100
+ and `tabMaterial Request`.material_request_type = 'Manufacture' """
+ },
+ "Work Order": {
+ "fields": """ production_item, item_name as production_item_name, planned_start_date,
+ stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """,
+ "filters": "docstatus = 1 and status not in ('Completed', 'Stopped')"
+ },
+}
+
+order_mapper = {
+ "Sales Order": {
+ "Delivery Date": "`tabSales Order Item`.delivery_date asc",
+ "Total Amount": "`tabSales Order`.base_grand_total desc"
+ },
+ "Material Request": {
+ "Required Date": "`tabMaterial Request Item`.schedule_date asc"
+ },
+ "Work Order": {
+ "Planned Start Date": "planned_start_date asc"
+ }
+}
+
+def execute(filters=None):
+ return ProductionPlanReport(filters).execute_report()
+
+class ProductionPlanReport(object):
+ def __init__(self, filters=None):
+ self.filters = frappe._dict(filters or {})
+ self.raw_materials_dict = {}
+ self.data = []
+
+ def execute_report(self):
+ self.get_open_orders()
+ self.get_raw_materials()
+ self.get_item_details()
+ self.get_bin_details()
+ self.get_purchase_details()
+ self.prepare_data()
+ self.get_columns()
+
+ return self.columns, self.data
+
+ def get_open_orders(self):
+ doctype = ("`tabWork Order`" if self.filters.based_on == "Work Order"
+ else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on))
+
+ filters = mapper.get(self.filters.based_on)["filters"]
+ filters = self.prepare_other_conditions(filters, self.filters.based_on)
+ order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by])
+
+ self.orders = frappe.db.sql(""" SELECT {fields} from {doctype}
+ WHERE {filters} {order_by}""".format(
+ doctype = doctype,
+ filters = filters,
+ order_by = order_by,
+ fields = mapper.get(self.filters.based_on)["fields"]
+ ), tuple(self.filters.docnames), as_dict=1)
+
+ def prepare_other_conditions(self, filters, doctype):
+ if self.filters.docnames:
+ field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype)
+ filters += " and %s in (%s)" % (field, ','.join(['%s'] * len(self.filters.docnames)))
+
+ if doctype != "Work Order":
+ filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype)
+
+ if self.filters.company:
+ filters += " and `tab%s`.company = %s" %(doctype, frappe.db.escape(self.filters.company))
+
+ return filters
+
+ def get_raw_materials(self):
+ if not self.orders: return
+ self.warehouses = [d.warehouse for d in self.orders]
+ self.item_codes = [d.production_item for d in self.orders]
+
+ if self.filters.based_on == "Work Order":
+ work_orders = [d.name for d in self.orders]
+
+ raw_materials = frappe.get_all("Work Order Item",
+ fields=["parent", "item_code", "item_name as raw_material_name",
+ "source_warehouse as warehouse", "required_qty"],
+ filters = {"docstatus": 1, "parent": ("in", work_orders), "source_warehouse": ("!=", "")}) or []
+ self.warehouses.extend([d.source_warehouse for d in raw_materials])
+
+ else:
+ bom_nos = []
+
+ for d in self.orders:
+ bom_no = d.bom_no or frappe.get_cached_value("Item", d.production_item, "default_bom")
+
+ if not d.bom_no:
+ d.bom_no = bom_no
+
+ bom_nos.append(bom_no)
+
+ bom_doctype = ("BOM Explosion Item"
+ if self.filters.include_subassembly_raw_materials else "BOM Item")
+
+ qty_field = ("qty_consumed_per_unit"
+ if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
+
+ raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
+ bom_item.item_name as raw_material_name, {0} as required_qty
+ FROM
+ `tabBOM` as bom, `tab{1}` as bom_item
+ WHERE
+ bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1
+ """.format(qty_field, bom_doctype, ','.join(["%s"] * len(bom_nos))), tuple(bom_nos), as_dict=1)
+
+ if not raw_materials: return
+
+ self.item_codes.extend([d.item_code for d in raw_materials])
+
+ for d in raw_materials:
+ if d.parent not in self.raw_materials_dict:
+ self.raw_materials_dict.setdefault(d.parent, [])
+
+ rows = self.raw_materials_dict[d.parent]
+ rows.append(d)
+
+ def get_item_details(self):
+ if not (self.orders and self.item_codes): return
+
+ self.item_details = {}
+ for d in frappe.get_all("Item Default", fields = ["parent", "default_warehouse"],
+ filters = {"company": self.filters.company, "parent": ("in", self.item_codes)}):
+ self.item_details[d.parent] = d
+
+ def get_bin_details(self):
+ if not (self.orders and self.raw_materials_dict): return
+
+ self.bin_details = {}
+ self.mrp_warehouses = []
+ if self.filters.raw_material_warehouse:
+ self.mrp_warehouses.extend(get_child_warehouses(self.filters.raw_material_warehouse))
+ self.warehouses.extend(self.mrp_warehouses)
+
+ for d in frappe.get_all("Bin",
+ fields=["warehouse", "item_code", "actual_qty", "ordered_qty", "projected_qty"],
+ filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)}):
+ key = (d.item_code, d.warehouse)
+ if key not in self.bin_details:
+ self.bin_details.setdefault(key, d)
+
+ def get_purchase_details(self):
+ if not (self.orders and self.raw_materials_dict): return
+
+ self.purchase_details = {}
+
+ for d in frappe.get_all("Purchase Order Item",
+ fields=["item_code", "min(schedule_date) as arrival_date", "qty as arrival_qty", "warehouse"],
+ filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)},
+ group_by = "item_code, warehouse"):
+ key = (d.item_code, d.warehouse)
+ if key not in self.purchase_details:
+ self.purchase_details.setdefault(key, d)
+
+ def prepare_data(self):
+ if not self.orders: return
+
+ for d in self.orders:
+ key = d.name if self.filters.based_on == "Work Order" else d.bom_no
+
+ if not self.raw_materials_dict.get(key): continue
+
+ bin_data = self.bin_details.get((d.production_item, d.warehouse)) or {}
+ d.update({
+ "for_warehouse": d.warehouse,
+ "available_qty": 0
+ })
+
+ if bin_data and bin_data.get("actual_qty") > 0 and d.qty_to_manufacture:
+ d.available_qty = (bin_data.get("actual_qty")
+ if (d.qty_to_manufacture > bin_data.get("actual_qty")) else d.qty_to_manufacture)
+
+ bin_data["actual_qty"] -= d.available_qty
+
+ self.update_raw_materials(d, key)
+
+ def update_raw_materials(self, data, key):
+ self.index = 0
+ self.raw_materials_dict.get(key)
+
+ warehouses = self.mrp_warehouses or []
+ for d in self.raw_materials_dict.get(key):
+ if self.filters.based_on != "Work Order":
+ d.required_qty = d.required_qty * data.qty_to_manufacture
+
+ if not warehouses:
+ warehouses = [data.warehouse]
+
+ if self.filters.based_on == "Work Order" and d.warehouse:
+ warehouses = [d.warehouse]
+ else:
+ item_details = self.item_details.get(d.item_code)
+ if item_details:
+ warehouses = [item_details["default_warehouse"]]
+
+ d.remaining_qty = d.required_qty
+ self.pick_materials_from_warehouses(d, data, warehouses)
+
+ if (d.remaining_qty and self.filters.raw_material_warehouse
+ and d.remaining_qty != d.required_qty):
+ row = self.get_args()
+ d.warehouse = self.filters.raw_material_warehouse
+ d.required_qty = d.remaining_qty
+ d.allotted_qty = 0
+ row.update(d)
+ self.data.append(row)
+
+ def pick_materials_from_warehouses(self, args, order_data, warehouses):
+ for index, warehouse in enumerate(warehouses):
+ if not args.remaining_qty: return
+
+ row = self.get_args()
+
+ key = (args.item_code, warehouse)
+ bin_data = self.bin_details.get(key)
+
+ if bin_data:
+ row.update(bin_data)
+
+ args.allotted_qty = 0
+ if bin_data and bin_data.get("actual_qty") > 0:
+ args.allotted_qty = (bin_data.get("actual_qty")
+ if (args.required_qty > bin_data.get("actual_qty")) else args.required_qty)
+
+ args.remaining_qty -= args.allotted_qty
+ bin_data["actual_qty"] -= args.allotted_qty
+
+ if ((self.mrp_warehouses and (args.allotted_qty or index == len(warehouses) - 1))
+ or not self.mrp_warehouses):
+ if not self.index:
+ row.update(order_data)
+ self.index += 1
+
+ args.warehouse = warehouse
+ row.update(args)
+ if self.purchase_details.get(key):
+ row.update(self.purchase_details.get(key))
+
+ self.data.append(row)
+
+ def get_args(self):
+ return frappe._dict({
+ "work_order": "",
+ "sales_order": "",
+ "production_item": "",
+ "production_item_name": "",
+ "qty_to_manufacture": "",
+ "produced_qty": ""
+ })
+
+ def get_columns(self):
+ based_on = self.filters.based_on
+
+ self.columns = [{
+ "label": _("ID"),
+ "options": based_on,
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "width": 100
+ }, {
+ "label": _("Item Code"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120
+ }, {
+ "label": _("Item Name"),
+ "fieldname": "production_item_name",
+ "fieldtype": "Data",
+ "width": 130
+ }, {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "for_warehouse",
+ "fieldtype": "Link",
+ "width": 100
+ }, {
+ "label": _("Order Qty"),
+ "fieldname": "qty_to_manufacture",
+ "fieldtype": "Float",
+ "width": 80
+ }, {
+ "label": _("Available"),
+ "fieldname": "available_qty",
+ "fieldtype": "Float",
+ "width": 80
+ }]
+
+ fieldname, fieldtype = "delivery_date", "Date"
+ if self.filters.based_on == "Sales Order" and self.filters.order_by == "Total Amount":
+ fieldname, fieldtype = "base_grand_total", "Currency"
+ elif self.filters.based_on == "Material Request":
+ fieldname = "schedule_date"
+ elif self.filters.based_on == "Work Order":
+ fieldname = "planned_start_date"
+
+ self.columns.append({
+ "label": _(self.filters.order_by),
+ "fieldname": fieldname,
+ "fieldtype": fieldtype,
+ "width": 100
+ })
+
+ self.columns.extend([{
+ "label": _("Raw Material Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120
+ }, {
+ "label": _("Raw Material Name"),
+ "fieldname": "raw_material_name",
+ "fieldtype": "Data",
+ "width": 130
+ }, {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "width": 110
+ }, {
+ "label": _("Required Qty"),
+ "fieldname": "required_qty",
+ "fieldtype": "Float",
+ "width": 100
+ }, {
+ "label": _("Allotted Qty"),
+ "fieldname": "allotted_qty",
+ "fieldtype": "Float",
+ "width": 100
+ }, {
+ "label": _("Expected Arrival Date"),
+ "fieldname": "arrival_date",
+ "fieldtype": "Date",
+ "width": 160
+ }, {
+ "label": _("Arrival Quantity"),
+ "fieldname": "arrival_qty",
+ "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/manufacturing/report/quality_inspection_summary/__init__.py b/erpnext/manufacturing/report/quality_inspection_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/__init__.py
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js
new file mode 100644
index 0000000..d4587aa
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Quality Inspection Summary"] = {
+ "filters": [
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -12),
+ reqd: 1
+ },
+ {
+ label: __("To Date"),
+ fieldname:"to_date",
+ fieldtype: "Date",
+ default: frappe.datetime.get_today(),
+ reqd: 1,
+ },
+ {
+ label: __("Status"),
+ fieldname: "status",
+ fieldtype: "Select",
+ options: ["", "Accepted", "Rejected"]
+ },
+ {
+ label: __("Item Code"),
+ fieldname: "item_code",
+ fieldtype: "Link",
+ options: "Item"
+ },
+ {
+ label: __("Inspected By"),
+ fieldname: "inspected_by",
+ fieldtype: "Link",
+ options: "User"
+ }
+ ]
+};
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json
new file mode 100644
index 0000000..48226e6
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-04-26 18:23:53.475110",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-26 18:24:50.529940",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Quality Inspection Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Quality Inspection",
+ "report_name": "Quality Inspection Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Quality Manager"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Stock Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
new file mode 100644
index 0000000..6192632
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
@@ -0,0 +1,132 @@
+# 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):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data , None, chart_data
+
+def get_data(filters):
+ query_filters = {"docstatus": ("<", 2)}
+
+ fields = ["name", "status", "report_date", "item_code", "item_name", "sample_size",
+ "inspection_type", "reference_type", "reference_name", "inspected_by"]
+
+ for field in ["status", "item_code", "status", "inspected_by"]:
+ if filters.get(field):
+ query_filters[field] = ("in", filters.get(field))
+
+ query_filters["report_date"] = (">=", filters.get("from_date"))
+ query_filters["report_date"] = ("<=", filters.get("to_date"))
+
+ return frappe.get_all("Quality Inspection",
+ fields= fields, filters=query_filters, order_by="report_date asc")
+
+def get_chart_data(periodic_data, columns):
+ labels = ["Rejected", "Accepted"]
+
+ status_wise_data = {
+ "Accepted": 0,
+ "Rejected": 0
+ }
+
+ datasets = []
+
+ for d in periodic_data:
+ status_wise_data[d.status] += 1
+
+ datasets.append({'name':'Qty Wise Chart',
+ 'values': [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")]})
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ "type": "donut",
+ "height": 300
+ }
+
+ return chart
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Id"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100
+ },
+ {
+ "label": _("Report Date"),
+ "fieldname": "report_date",
+ "fieldtype": "Date",
+ "width": 150
+ }
+ ]
+
+ if not filters.get("status"):
+ columns.append(
+ {
+ "label": _("Status"),
+ "fieldname": "status",
+ "width": 100
+ },
+ )
+
+ columns.extend([
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 130
+ },
+ {
+ "label": _("Sample Size"),
+ "fieldname": "sample_size",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ {
+ "label": _("Inspection Type"),
+ "fieldname": "inspection_type",
+ "fieldtype": "Data",
+ "width": 110
+ },
+ {
+ "label": _("Document Type"),
+ "fieldname": "reference_type",
+ "fieldtype": "Data",
+ "width": 90
+ },
+ {
+ "label": _("Document Name"),
+ "fieldname": "reference_name",
+ "fieldtype": "Dynamic Link",
+ "options": "reference_type",
+ "width": 150
+ },
+ {
+ "label": _("Inspected By"),
+ "fieldname": "inspected_by",
+ "fieldtype": "Link",
+ "options": "User",
+ "width": 150
+ }
+ ])
+
+ return columns
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/work_order_summary/__init__.py b/erpnext/manufacturing/report/work_order_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/__init__.py
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js
new file mode 100644
index 0000000..ec9fe35
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js
@@ -0,0 +1,65 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Work Order Summary"] = {
+ "filters": [
+ {
+ label: __("Company"),
+ fieldname: "company",
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -12),
+ reqd: 1
+ },
+ {
+ label: __("To Date"),
+ fieldname:"to_date",
+ fieldtype: "Date",
+ default: frappe.datetime.get_today(),
+ reqd: 1,
+ },
+ {
+ label: __("Status"),
+ fieldname: "status",
+ fieldtype: "Select",
+ options: ["", "Not Started", "In Process", "Completed", "Stopped"]
+ },
+ {
+ label: __("Sales Orders"),
+ fieldname: "sales_order",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Sales Order', txt);
+ }
+ },
+ {
+ label: __("Production Item"),
+ fieldname: "production_item",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Item', txt);
+ }
+ },
+ {
+ label: __("Age"),
+ fieldname:"age",
+ fieldtype: "Int",
+ default: "0"
+ },
+ {
+ label: __("Charts Based On"),
+ fieldname:"charts_based_on",
+ fieldtype: "Select",
+ options: ["Status", "Age", "Quantity"],
+ default: "Status"
+ },
+ ]
+};
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.json b/erpnext/manufacturing/report/work_order_summary/work_order_summary.json
new file mode 100644
index 0000000..0d093e2
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-04-17 17:07:56.830358",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-19 16:59:47.979278",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Work Order Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Order",
+ "report_name": "Work Order Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
new file mode 100644
index 0000000..bc09ed4
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
@@ -0,0 +1,270 @@
+# 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.utils import date_diff, today, getdate, flt
+from frappe import _
+from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period)
+
+def execute(filters=None):
+ columns, data = [], []
+
+ if not filters.get("age"):
+ filters["age"] = 0
+
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data, None, chart_data
+
+def get_data(filters):
+ query_filters = {"docstatus": 1}
+
+ fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty",
+ "planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"]
+
+ for field in ["sales_order", "production_item", "status", "company"]:
+ if filters.get(field):
+ query_filters[field] = ("in", filters.get(field))
+
+ query_filters["planned_start_date"] = (">=", filters.get("from_date"))
+ query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
+
+ data = frappe.get_all("Work Order",
+ fields= fields, filters=query_filters, order_by="planned_start_date asc")
+
+ res = []
+ for d in data:
+ start_date = d.actual_start_date or d.planned_start_date
+ d.age = 0
+
+ if d.status != 'Completed':
+ d.age = date_diff(today(), start_date)
+
+ if filters.get("age") <= d.age:
+ res.append(d)
+
+ return res
+
+def get_chart_data(data, filters):
+ if filters.get("charts_based_on") == "Status":
+ return get_chart_based_on_status(data)
+ elif filters.get("charts_based_on") == "Age":
+ return get_chart_based_on_age(data)
+ else:
+ return get_chart_based_on_qty(data, filters)
+
+def get_chart_based_on_status(data):
+ labels = ["Not Started", "In Process", "Stopped", "Completed"]
+
+ status_wise_data = {
+ "Not Started": 0,
+ "In Process": 0,
+ "Stopped": 0,
+ "Completed": 0
+ }
+
+ for d in data:
+ if d.status == "In Process" and d.produced_qty:
+ status_wise_data["Completed"] += d.produced_qty
+
+ status_wise_data[d.status] += d.qty
+
+ values = [status_wise_data["Not Started"], status_wise_data["In Process"],
+ status_wise_data["Stopped"], status_wise_data["Completed"]]
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': [{'name':'Qty Wise Chart', 'values': values}]
+ },
+ "type": "donut",
+ "height": 300
+ }
+
+ return chart
+
+def get_chart_based_on_age(data):
+ labels = ["0-30 Days", "30-60 Days", "60-90 Days", "90 Above"]
+
+ age_wise_data = {
+ "0-30 Days": 0,
+ "30-60 Days": 0,
+ "60-90 Days": 0,
+ "90 Above": 0
+ }
+
+ for d in data:
+ if d.age > 0 and d.age <= 30:
+ age_wise_data["0-30 Days"] += 1
+ elif d.age > 30 and d.age <= 60:
+ age_wise_data["30-60 Days"] += 1
+ elif d.age > 60 and d.age <= 90:
+ age_wise_data["60-90 Days"] += 1
+ else:
+ age_wise_data["90 Above"] += 1
+
+ values = [age_wise_data["0-30 Days"], age_wise_data["30-60 Days"],
+ age_wise_data["60-90 Days"], age_wise_data["90 Above"]]
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': [{'name':'Qty Wise Chart', 'values': values}]
+ },
+ "type": "donut",
+ "height": 300
+ }
+
+ return chart
+
+def get_chart_based_on_qty(data, filters):
+ labels, periodic_data = prepare_chart_data(data, filters)
+
+ pending, completed = [], []
+ datasets = []
+
+ for d in labels:
+ pending.append(periodic_data.get("Pending").get(d))
+ completed.append(periodic_data.get("Completed").get(d))
+
+ datasets.append({"name": "Pending", "values": pending})
+ datasets.append({"name": "Completed", "values": completed})
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ "type": "bar",
+ "barOptions": {
+ "stacked": 1
+ }
+ }
+
+ return chart
+
+def prepare_chart_data(data, filters):
+ labels = []
+
+ periodic_data = {
+ "Pending": {},
+ "Completed": {}
+ }
+
+ filters.range = "Monthly"
+
+ ranges = get_period_date_ranges(filters)
+ for from_date, end_date in ranges:
+ period = get_period(end_date, filters)
+ if period not in labels:
+ labels.append(period)
+
+ if period not in periodic_data["Pending"]:
+ periodic_data["Pending"][period] = 0
+
+ if period not in periodic_data["Completed"]:
+ periodic_data["Completed"][period] = 0
+
+ for d in data:
+ if getdate(d.planned_start_date) >= from_date and getdate(d.planned_start_date) <= end_date:
+ periodic_data["Pending"][period] += (flt(d.qty) - flt(d.produced_qty))
+ periodic_data["Completed"][period] += flt(d.produced_qty)
+
+ return labels, periodic_data
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Id"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100
+ },
+ ]
+
+ if not filters.get("status"):
+ columns.append(
+ {
+ "label": _("Status"),
+ "fieldname": "status",
+ "width": 100
+ },
+ )
+
+ columns.extend([
+ {
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130
+ },
+ {
+ "label": _("Produce Qty"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ {
+ "label": _("Produced Qty"),
+ "fieldname": "produced_qty",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ {
+ "label": _("Sales Order"),
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "options": "Sales Order",
+ "width": 90
+ },
+ {
+ "label": _("Planned Start Date"),
+ "fieldname": "planned_start_date",
+ "fieldtype": "Date",
+ "width": 150
+ },
+ {
+ "label": _("Planned End Date"),
+ "fieldname": "planned_end_date",
+ "fieldtype": "Date",
+ "width": 150
+ }
+ ])
+
+ if filters.get("status") != 'Not Started':
+ columns.extend([
+ {
+ "label": _("Actual Start Date"),
+ "fieldname": "actual_start_date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Actual End Date"),
+ "fieldname": "actual_end_date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Age"),
+ "fieldname": "age",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ ])
+
+ if filters.get("status") == 'Completed':
+ columns.extend([
+ {
+ "label": _("Lead Time (in mins)"),
+ "fieldname": "lead_time",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ ])
+
+ return columns
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index accfa69..af7cb8e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -623,7 +623,7 @@
erpnext.patches.v12_0.update_due_date_in_gle
erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
erpnext.patches.v12_0.update_ewaybill_field_position
-erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes
+erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes #2020-05-11
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
erpnext.patches.v12_0.move_plaid_settings_to_doctype
execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_link')
@@ -678,6 +678,16 @@
erpnext.patches.v12_0.fix_quotation_expired_status
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype
+erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
execute:frappe.delete_doc_if_exists("Page", "appointment-analytic")
+execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True)
+erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price
+erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation
+erpnext.patches.v12_0.set_serial_no_status
+erpnext.patches.v12_0.update_price_list_currency_in_bom
+execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
+erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
+erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes
+erpnext.patches.v12_0.update_bom_in_so_mr
execute:frappe.delete_doc("Report", "Department Analytics")
diff --git a/erpnext/patches/v11_0/set_default_email_template_in_hr.py b/erpnext/patches/v11_0/set_default_email_template_in_hr.py
index 14954fb..4622376 100644
--- a/erpnext/patches/v11_0/set_default_email_template_in_hr.py
+++ b/erpnext/patches/v11_0/set_default_email_template_in_hr.py
@@ -1,8 +1,9 @@
from __future__ import unicode_literals
+from frappe import _
import frappe
def execute():
hr_settings = frappe.get_single("HR Settings")
- hr_settings.leave_approval_notification_template = "Leave Approval Notification"
- hr_settings.leave_status_notification_template = "Leave Status Notification"
- hr_settings.save()
\ No newline at end of file
+ hr_settings.leave_approval_notification_template = _("Leave Approval Notification")
+ hr_settings.leave_status_notification_template = _("Leave Status Notification")
+ hr_settings.save()
diff --git a/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py b/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py
index b71ea66..657decf 100644
--- a/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py
+++ b/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py
@@ -20,7 +20,8 @@
else:
insert_after_field = 'accounting_dimensions_section'
- for doctype in ["Subscription Plan", "Subscription", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item"]:
+ for doctype in ["Subscription Plan", "Subscription", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item",
+ "Expense Claim Detail", "Expense Taxes and Charges"]:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
diff --git a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py
new file mode 100644
index 0000000..98a2fcf
--- /dev/null
+++ b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py
@@ -0,0 +1,44 @@
+# Copyright (c) 2018, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ """Delete duplicate leave ledger entries of type allocation created."""
+ if not frappe.db.a_row_exists("Leave Ledger Entry"):
+ return
+
+ duplicate_records_list = get_duplicate_records()
+ delete_duplicate_ledger_entries(duplicate_records_list)
+
+def get_duplicate_records():
+ """Fetch all but one duplicate records from the list of expired leave allocation."""
+ return frappe.db.sql_list("""
+ WITH duplicate_records AS
+ (SELECT
+ name, transaction_name, is_carry_forward,
+ ROW_NUMBER() over(partition by transaction_name order by creation)as row
+ FROM `tabLeave Ledger Entry` l
+ WHERE (EXISTS
+ (SELECT name
+ FROM `tabLeave Ledger Entry`
+ WHERE
+ transaction_name = l.transaction_name
+ AND transaction_type = 'Leave Allocation'
+ AND name <> l.name
+ AND employee = l.employee
+ AND docstatus = 1
+ AND leave_type = l.leave_type
+ AND is_carry_forward=l.is_carry_forward
+ AND to_date = l.to_date
+ AND from_date = l.from_date
+ AND is_expired = 1
+ )))
+ SELECT name FROM duplicate_records WHERE row > 1
+ """)
+
+def delete_duplicate_ledger_entries(duplicate_records_list):
+ """Delete duplicate leave ledger entries."""
+ if duplicate_records_list:
+ frappe.db.sql(''' DELETE FROM `tabLeave Ledger Entry` WHERE name in {0}'''.format(tuple(duplicate_records_list))) #nosec
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/set_serial_no_status.py b/erpnext/patches/v12_0/set_serial_no_status.py
new file mode 100644
index 0000000..4ec84ef
--- /dev/null
+++ b/erpnext/patches/v12_0/set_serial_no_status.py
@@ -0,0 +1,17 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import getdate, nowdate
+
+def execute():
+ frappe.reload_doc('stock', 'doctype', 'serial_no')
+
+ for serial_no in frappe.db.sql("""select name, delivery_document_type, warranty_expiry_date from `tabSerial No`
+ where (status is NULL OR status='')""", as_dict = 1):
+ if serial_no.get("delivery_document_type"):
+ status = "Delivered"
+ elif serial_no.get("warranty_expiry_date") and getdate(serial_no.get("warranty_expiry_date")) <= getdate(nowdate()):
+ status = "Expired"
+ else:
+ status = "Active"
+
+ frappe.db.set_value("Serial No", serial_no.get("name"), "status", status)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py
new file mode 100644
index 0000000..4a6e228
--- /dev/null
+++ b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc("buying", "doctype", "supplier_quotation")
+ frappe.db.sql("""UPDATE `tabSupplier Quotation`
+ SET valid_till = DATE_ADD(transaction_date , INTERVAL 1 MONTH)
+ WHERE docstatus < 2""")
diff --git a/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py
new file mode 100644
index 0000000..60aec05
--- /dev/null
+++ b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py
@@ -0,0 +1,15 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ invalid_selling_item_price = frappe.db.sql(
+ """SELECT name FROM `tabItem Price` WHERE selling = 1 and buying = 0 and (supplier IS NOT NULL or supplier = '')"""
+ )
+ invalid_buying_item_price = frappe.db.sql(
+ """SELECT name FROM `tabItem Price` WHERE selling = 0 and buying = 1 and (customer IS NOT NULL or customer = '')"""
+ )
+ docs_to_modify = invalid_buying_item_price + invalid_selling_item_price
+ for d in docs_to_modify:
+ # saving the doc will auto reset invalid customer/supplier field
+ doc = frappe.get_doc("Item Price", d[0])
+ doc.save()
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/update_bom_in_so_mr.py b/erpnext/patches/v12_0/update_bom_in_so_mr.py
new file mode 100644
index 0000000..309ae4c
--- /dev/null
+++ b/erpnext/patches/v12_0/update_bom_in_so_mr.py
@@ -0,0 +1,19 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc("stock", "doctype", "material_request_item")
+ frappe.reload_doc("selling", "doctype", "sales_order_item")
+
+ for doctype in ["Sales Order", "Material Request"]:
+ condition = " and child_doc.stock_qty > child_doc.produced_qty"
+ if doctype == "Material Request":
+ condition = " and doc.per_ordered < 100 and doc.material_request_type = 'Manufacture'"
+
+ frappe.db.sql(""" UPDATE `tab{doc}` as doc, `tab{doc} Item` as child_doc, tabItem as item
+ SET
+ child_doc.bom_no = item.default_bom
+ WHERE
+ child_doc.item_code = item.name and child_doc.docstatus < 2
+ and item.default_bom is not null and item.default_bom != '' {cond}
+ """.format(doc = doctype, cond = condition))
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/update_price_list_currency_in_bom.py b/erpnext/patches/v12_0/update_price_list_currency_in_bom.py
new file mode 100644
index 0000000..f5e7b94
--- /dev/null
+++ b/erpnext/patches/v12_0/update_price_list_currency_in_bom.py
@@ -0,0 +1,31 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import getdate, flt
+from erpnext.setup.utils import get_exchange_rate
+
+def execute():
+ frappe.reload_doc("manufacturing", "doctype", "bom")
+ frappe.reload_doc("manufacturing", "doctype", "bom_item")
+
+ frappe.db.sql(""" UPDATE `tabBOM`, `tabPrice List`
+ SET
+ `tabBOM`.price_list_currency = `tabPrice List`.currency,
+ `tabBOM`.plc_conversion_rate = 1.0
+ WHERE
+ `tabBOM`.buying_price_list = `tabPrice List`.name AND `tabBOM`.docstatus < 2
+ AND `tabBOM`.rm_cost_as_per = 'Price List'
+ """)
+
+ for d in frappe.db.sql("""
+ SELECT
+ bom.creation, bom.name, bom.price_list_currency as currency,
+ company.default_currency as company_currency
+ FROM
+ `tabBOM` as bom, `tabCompany` as company
+ WHERE
+ bom.company = company.name AND bom.rm_cost_as_per = 'Price List' AND
+ bom.price_list_currency != company.default_currency AND bom.docstatus < 2""", as_dict=1):
+ plc_conversion_rate = get_exchange_rate(d.currency,
+ d.company_currency, getdate(d.creation), "for_buying")
+
+ frappe.db.set_value("BOM", d.name, "plc_conversion_rate", plc_conversion_rate)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/__init__.py b/erpnext/patches/v13_0/__init__.py
index e69de29..baffc48 100644
--- a/erpnext/patches/v13_0/__init__.py
+++ b/erpnext/patches/v13_0/__init__.py
@@ -0,0 +1 @@
+from __future__ import unicode_literals
diff --git a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
index 179be2c..ec94cd0 100644
--- a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
+++ b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
@@ -7,7 +7,7 @@
from frappe.model.utils.rename_field import rename_field
def execute():
- if not frappe.db.table_exists("Payroll Period"):
+ if not (frappe.db.table_exists("Payroll Period") and frappe.db.table_exists("Taxable Salary Slab")):
return
for doctype in ("income_tax_slab", "salary_structure_assignment", "employee_other_income", "income_tax_slab_other_charges"):
@@ -60,6 +60,9 @@
""", (income_tax_slab.name, company.name, period.start_date))
# move other incomes to separate document
+ if not frappe.db.table_exists("Employee Tax Exemption Proof Submission"):
+ return
+
migrated = []
proofs = frappe.get_all("Employee Tax Exemption Proof Submission",
filters = {'docstatus': 1},
@@ -79,6 +82,9 @@
except:
pass
+ if not frappe.db.table_exists("Employee Tax Exemption Declaration"):
+ return
+
declerations = frappe.get_all("Employee Tax Exemption Declaration",
filters = {'docstatus': 1},
fields =['payroll_period', 'employee', 'company', 'income_from_other_sources']
diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
new file mode 100644
index 0000000..2c646b1
--- /dev/null
+++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ company = frappe.db.get_single_value('Global Defaults', 'default_company')
+ doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Patient Appointment', 'Patient Encounter', 'Vital Signs']
+ for entry in doctypes:
+ if frappe.db.exists('DocType', entry):
+ frappe.db.sql("update `tab{dt}` set company = '{company}' where ifnull(company, '') = ''".format(dt=entry, company=company))
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
new file mode 100644
index 0000000..331c559
--- /dev/null
+++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
@@ -0,0 +1,42 @@
+
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+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")
+ frappe.reload_doc("manufacturing", "doctype", "work_order_item")
+ frappe.reload_doc("manufacturing", "doctype", "job_card")
+
+ data = frappe.get_all("Work Order",
+ filters = {
+ "docstatus": 1,
+ "status": ("in", ["In Process", "Completed"])
+ })
+
+ for d in data:
+ doc = frappe.get_doc("Work Order", d.name)
+ doc.set_actual_dates()
+ doc.db_set("actual_start_date", doc.actual_start_date, update_modified=False)
+
+ if doc.status == "Completed":
+ frappe.db.set_value("Work Order", d.name, {
+ "actual_end_date": doc.actual_end_date,
+ "lead_time": doc.lead_time
+ }, update_modified=False)
+
+ if not doc.planned_end_date:
+ planned_end_date = add_to_date(doc.planned_start_date, minutes=doc.lead_time)
+ doc.db_set("planned_end_date", doc.actual_start_date, update_modified=False)
+
+ frappe.db.sql(""" UPDATE `tabJob Card` as jc, `tabWork Order` as wo
+ SET
+ jc.production_item = wo.production_item, jc.item_name = wo.item_name
+ WHERE
+ jc.work_order = wo.name and IFNULL(jc.production_item, "") = ""
+ """)
\ No newline at end of file
diff --git a/erpnext/projects/dashboard_fixtures.py b/erpnext/projects/dashboard_fixtures.py
new file mode 100644
index 0000000..d89ffe9
--- /dev/null
+++ b/erpnext/projects/dashboard_fixtures.py
@@ -0,0 +1,50 @@
+# 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/desk_page/projects/projects.json b/erpnext/projects/desk_page/projects/projects.json
index a07cdff..fdbe13b 100644
--- a/erpnext/projects/desk_page/projects/projects.json
+++ b/erpnext/projects/desk_page/projects/projects.json
@@ -17,18 +17,23 @@
}
],
"category": "Modules",
- "charts": [],
+ "charts": [
+ {
+ "chart_name": "Project Summary",
+ "label": "Open Projects"
+ }
+ ],
"creation": "2020-03-02 15:46:04.874669",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Projects",
- "modified": "2020-04-01 11:28:51.245756",
+ "modified": "2020-05-19 21:09:52.031828",
"modified_by": "Administrator",
"module": "Projects",
"name": "Projects",
@@ -37,6 +42,7 @@
"pin_to_top": 0,
"shortcuts": [
{
+ "color": "#4d4da8",
"format": "{} Assigned",
"label": "Task",
"link_to": "Task",
@@ -44,8 +50,11 @@
"type": "DocType"
},
{
+ "color": "#4d4da8",
+ "format": "{} Open",
"label": "Project",
"link_to": "Project",
+ "stats_filter": "{\n \"status\": \"Open\"\n}",
"type": "DocType"
},
{
@@ -57,6 +66,11 @@
"label": "Project Billing Summary",
"link_to": "Project Billing Summary",
"type": "Report"
+ },
+ {
+ "label": "Project Dashboard",
+ "link_to": "Project",
+ "type": "Dashboard"
}
]
}
\ No newline at end of file
diff --git a/erpnext/projects/report/project_summary/__init__.py b/erpnext/projects/report/project_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/projects/report/project_summary/__init__.py
diff --git a/erpnext/projects/report/project_summary/project_summary.js b/erpnext/projects/report/project_summary/project_summary.js
new file mode 100644
index 0000000..15367ac
--- /dev/null
+++ b/erpnext/projects/report/project_summary/project_summary.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["Project Summary"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ "fieldname": "status",
+ "label": __("Status"),
+ "fieldtype": "Select",
+ "options": "Open\nComplete\nCancelled",
+ "default": "Open"
+ }
+ ]
+};
diff --git a/erpnext/projects/report/project_summary/project_summary.json b/erpnext/projects/report/project_summary/project_summary.json
new file mode 100644
index 0000000..0b18b3e
--- /dev/null
+++ b/erpnext/projects/report/project_summary/project_summary.json
@@ -0,0 +1,27 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-05-04 19:31:54.575765",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-05-04 19:32:53.177213",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Project Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Project",
+ "report_name": "Project Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Projects User"
+ },
+ {
+ "role": "Projects Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py
new file mode 100644
index 0000000..a20d7f2
--- /dev/null
+++ b/erpnext/projects/report/project_summary/project_summary.py
@@ -0,0 +1,148 @@
+# 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):
+ columns = get_columns()
+ data = []
+
+ data = frappe.db.get_all("Project", filters=filters, fields=["name", 'status', "percent_complete", "expected_start_date", "expected_end_date"], order_by="expected_end_date")
+
+ for project in data:
+ project["total_tasks"] = frappe.db.count("Task", filters={"project": project.name})
+ project["completed_tasks"] = frappe.db.count("Task", filters={"project": project.name, "status": "Completed"})
+ project["overdue_tasks"] = frappe.db.count("Task", filters={"project": project.name, "status": "Overdue"})
+
+ chart = get_chart_data(data)
+ report_summary = get_report_summary(data)
+
+ return columns, data, None, chart, report_summary
+
+def get_columns():
+ return [
+ {
+ "fieldname": "name",
+ "label": _("Project"),
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 200
+ },
+ {
+ "fieldname": "status",
+ "label": _("Status"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "total_tasks",
+ "label": _("Total Tasks"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "completed_tasks",
+ "label": _("Tasks Completed"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "overdue_tasks",
+ "label": _("Tasks Overdue"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "percent_complete",
+ "label": _("Completion"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "expected_start_date",
+ "label": _("Start Date"),
+ "fieldtype": "Date",
+ "width": 120
+ },
+ {
+ "fieldname": "expected_end_date",
+ "label": _("End Date"),
+ "fieldtype": "Date",
+ "width": 120
+ },
+ ]
+
+def get_chart_data(data):
+ labels = []
+ total = []
+ completed = []
+ overdue = []
+
+ for project in data:
+ labels.append(project.name)
+ total.append(project.total_tasks)
+ completed.append(project.completed_tasks)
+ overdue.append(project.overdue_tasks)
+
+ return {
+ "data": {
+ 'labels': labels,
+ 'datasets': [
+ {
+ "name": "Overdue",
+ "values": overdue
+ },
+ {
+ "name": "Completed",
+ "values": completed
+ },
+ {
+ "name": "Total Tasks",
+ "values": total
+ },
+ ]
+ },
+ "type": "bar",
+ "colors": ["#fc4f51", "#78d6ff", "#7575ff"],
+ "barOptions": {
+ "stacked": True
+ }
+ }
+
+def get_report_summary(data):
+ if not data:
+ return None
+
+ avg_completion = sum([project.percent_complete for project in data]) / len(data)
+ total = sum([project.total_tasks for project in data])
+ total_overdue = sum([project.overdue_tasks for project in data])
+ completed = sum([project.completed_tasks for project in data])
+
+ return [
+ {
+ "value": avg_completion,
+ "indicator": "Green" if avg_completion > 50 else "Red",
+ "label": "Average Completion",
+ "datatype": "Percent",
+ },
+ {
+ "value": total,
+ "indicator": "Blue",
+ "label": "Total Tasks",
+ "datatype": "Int",
+ },
+ {
+ "value": completed,
+ "indicator": "Green",
+ "label": "Completed Tasks",
+ "datatype": "Int",
+ },
+ {
+ "value": total_overdue,
+ "indicator": "Green" if total_overdue == 0 else "Red",
+ "label": "Overdue Tasks",
+ "datatype": "Int",
+ }
+ ]
diff --git a/erpnext/public/js/purchase_trends_filters.js b/erpnext/public/js/purchase_trends_filters.js
index cd767f5..c786a86 100644
--- a/erpnext/public/js/purchase_trends_filters.js
+++ b/erpnext/public/js/purchase_trends_filters.js
@@ -51,7 +51,10 @@
{ "value": "Supplier Group", "label": __("Supplier Group") },
{ "value": "Project", "label": __("Project") }
],
- "default": "Item"
+ "default": "Item",
+ "dashboard_config": {
+ "read_only": 1
+ }
},
{
"fieldname":"group_by",
diff --git a/erpnext/public/js/sales_trends_filters.js b/erpnext/public/js/sales_trends_filters.js
index b272fdd..b9c4dca 100644
--- a/erpnext/public/js/sales_trends_filters.js
+++ b/erpnext/public/js/sales_trends_filters.js
@@ -27,7 +27,10 @@
{ "value": "Territory", "label": __("Territory") },
{ "value": "Project", "label": __("Project") }
],
- "default": "Item"
+ "default": "Item",
+ "dashboard_config": {
+ "read_only": 1,
+ }
},
{
"fieldname":"group_by",
diff --git a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json
index 0849fd7..7691fe3 100644
--- a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json
+++ b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json
@@ -1,10 +1,12 @@
{
- "autoname": "format:MTNG-{date}",
+ "actions": [],
+ "autoname": "naming_series:",
"creation": "2018-10-15 16:25:41.548432",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
+ "naming_series",
"date",
"cb_00",
"status",
@@ -53,9 +55,16 @@
"fieldname": "sb_01",
"fieldtype": "Section Break",
"label": "Minutes"
+ },
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "options": "MTNG-.YYYY.-.MM.-.DD.-"
}
],
- "modified": "2019-07-13 19:57:40.500541",
+ "links": [],
+ "modified": "2020-05-19 13:18:59.821740",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Meeting",
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 3309858..732780a 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -251,8 +251,7 @@
def calculate_annual_eligible_hra_exemption(doc):
- basic_component = frappe.get_cached_value('Company', doc.company, "basic_component")
- hra_component = frappe.get_cached_value('Company', doc.company, "hra_component")
+ basic_component, hra_component = frappe.db.get_value('Company', doc.company, ["basic_component", "hra_component"])
if not (basic_component and hra_component):
frappe.throw(_("Please mention Basic and HRA component in Company"))
annual_exemption, monthly_exemption, hra_amount = 0, 0, 0
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index fd1cc58..43b1ea8 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -117,12 +117,18 @@
else:
row.append(invoice_details.get(fieldname))
taxable_value = 0
+
+ if invoice in self.cgst_igst_invoices:
+ division_factor = 2
+ else:
+ division_factor = 1
+
for item_code, net_amount in self.invoice_items.get(invoice).items():
- if item_code in items:
- if self.item_tax_rate.get(invoice) and tax_rate in self.item_tax_rate.get(invoice, {}).get(item_code, []):
- taxable_value += abs(net_amount)
- elif not self.item_tax_rate.get(invoice):
- taxable_value += abs(net_amount)
+ if item_code in items:
+ if self.item_tax_rate.get(invoice) and tax_rate/division_factor in self.item_tax_rate.get(invoice, {}).get(item_code, []):
+ taxable_value += abs(net_amount)
+ elif not self.item_tax_rate.get(invoice):
+ taxable_value += abs(net_amount)
row += [tax_rate or 0, taxable_value]
@@ -196,7 +202,7 @@
if d.item_code not in self.invoice_items.get(d.parent, {}):
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
sum(i.get('base_net_amount', 0) for i in items
- if i.item_code == d.item_code and i.parent == d.parent))
+ if i.item_code == d.item_code and i.parent == d.parent))
item_tax_rate = {}
@@ -221,6 +227,8 @@
self.items_based_on_tax_rate = {}
self.invoice_cess = frappe._dict()
+ self.cgst_igst_invoices = []
+
unidentified_gst_accounts = []
for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
if account in self.gst_accounts.cess_account:
@@ -243,6 +251,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)
rate_based_dict = self.items_based_on_tax_rate\
.setdefault(parent, {}).setdefault(tax_rate, [])
diff --git a/erpnext/selling/desk_page/selling/selling.json b/erpnext/selling/desk_page/selling/selling.json
deleted file mode 100644
index a20806b..0000000
--- a/erpnext/selling/desk_page/selling/selling.json
+++ /dev/null
@@ -1,85 +0,0 @@
-{
- "cards": [
- {
- "hidden": 0,
- "label": "Items and Pricing",
- "links": "[\n {\n \"description\": \"All Products or Services.\",\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Price List\"\n ],\n \"description\": \"Multiple Item prices.\",\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"onboard\": 1,\n \"route\": \"#Report/Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Price List master.\",\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of Item Groups.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Bundle items at time of sale.\",\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying different promotional schemes.\",\n \"label\": \"Promotional Scheme\",\n \"name\": \"Promotional Scheme\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Rules for applying pricing and discount.\",\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for adding shipping costs.\",\n \"label\": \"Shipping Rule\",\n \"name\": \"Shipping Rule\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Define coupon codes.\",\n \"label\": \"Coupon Code\",\n \"name\": \"Coupon Code\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Settings",
- "links": "[\n {\n \"description\": \"Default settings for selling transactions.\",\n \"label\": \"Selling Settings\",\n \"name\": \"Selling Settings\",\n \"settings\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Template of terms or contract.\",\n \"label\": \"Terms and Conditions Template\",\n \"name\": \"Terms and Conditions\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax template for selling transactions.\",\n \"label\": \"Sales Taxes and Charges Template\",\n \"name\": \"Sales Taxes and Charges Template\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Track Leads by Lead Source.\",\n \"label\": \"Lead Source\",\n \"name\": \"Lead Source\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Customer Group Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Customer Group\",\n \"link\": \"Tree/Customer Group\",\n \"name\": \"Customer Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Contacts.\",\n \"label\": \"Contact\",\n \"name\": \"Contact\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Addresses.\",\n \"label\": \"Address\",\n \"name\": \"Address\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Territory Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Territory\",\n \"link\": \"Tree/Territory\",\n \"name\": \"Territory\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sales campaigns.\",\n \"label\": \"Campaign\",\n \"name\": \"Campaign\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Other Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Customer Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"route_options\": {\n \"party_type\": \"Customer\"\n },\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Search\",\n \"name\": \"BOM Search\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Available Stock for Packing Items\",\n \"name\": \"Available Stock for Packing Items\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Pending SO Items For Purchase Request\",\n \"name\": \"Pending SO Items For Purchase Request\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customers Without Any Sales Transactions\",\n \"name\": \"Customers Without Any Sales Transactions\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Sales",
- "links": "[\n {\n \"description\": \"Customer Database.\",\n \"label\": \"Customer\",\n \"name\": \"Customer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Quotes to Leads or Customers.\",\n \"label\": \"Quotation\",\n \"name\": \"Quotation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Confirmed orders from Customers.\",\n \"label\": \"Sales Order\",\n \"name\": \"Sales Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Invoices for Costumers.\",\n \"label\": \"Sales Invoice\",\n \"name\": \"Sales Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Blanket Orders from Costumers.\",\n \"label\": \"Blanket Order\",\n \"name\": \"Blanket Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Manage Sales Partners.\",\n \"label\": \"Sales Partner\",\n \"name\": \"Sales Partner\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Manage Sales Person Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Sales Person\",\n \"link\": \"Tree/Sales Person\",\n \"name\": \"Sales Person\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Key Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Analytics\",\n \"name\": \"Sales Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"icon\": \"fa fa-bar-chart\",\n \"is_query_report\": true,\n \"label\": \"Customer Acquisition and Loyalty\",\n \"name\": \"Customer Acquisition and Loyalty\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Ordered Items To Be Delivered\",\n \"name\": \"Ordered Items To Be Delivered\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Person-wise Transaction Summary\",\n \"name\": \"Sales Person-wise Transaction Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Sales History\",\n \"name\": \"Item-wise Sales History\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Quotation\"\n ],\n \"doctype\": \"Quotation\",\n \"is_query_report\": true,\n \"label\": \"Quotation Trends\",\n \"name\": \"Quotation Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Trends\",\n \"name\": \"Sales Order Trends\",\n \"type\": \"report\"\n }\n]"
- }
- ],
- "category": "Modules",
- "charts": [
- {
- "chart_name": "Income",
- "label": "Income"
- }
- ],
- "creation": "2020-01-28 11:49:12.092882",
- "developer_mode_only": 0,
- "disable_user_customization": 0,
- "docstatus": 0,
- "doctype": "Desk Page",
- "extends_another_page": 0,
- "icon": "",
- "idx": 0,
- "is_standard": 1,
- "label": "Selling",
- "modified": "2020-04-01 11:28:51.047373",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "Selling",
- "owner": "Administrator",
- "pin_to_bottom": 0,
- "pin_to_top": 0,
- "shortcuts": [
- {
- "label": "Sales Invoice",
- "link_to": "Sales Invoice",
- "type": "DocType"
- },
- {
- "label": "Sales Order",
- "link_to": "Sales Order",
- "type": "DocType"
- },
- {
- "label": "Quotation",
- "link_to": "Quotation",
- "type": "DocType"
- },
- {
- "label": "Delivery Note",
- "link_to": "Delivery Note",
- "type": "DocType"
- },
- {
- "label": "Accounts Receivable",
- "link_to": "Accounts Receivable",
- "type": "Report"
- },
- {
- "label": "Sales Register",
- "link_to": "Sales Register",
- "type": "Report"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 50e719f..a6889e0 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -165,6 +165,10 @@
contact.mobile_no = lead.mobile_no
contact.is_primary_contact = 1
contact.append('links', dict(link_doctype='Customer', link_name=self.name))
+ if lead.email_id:
+ contact.append('email_ids', dict(email_id=lead.email_id, is_primary=1))
+ if lead.mobile_no:
+ contact.append('phone_nos', dict(phone=lead.mobile_no, is_primary_mobile_no=1))
contact.flags.ignore_permissions = self.flags.ignore_permissions
contact.autoname()
if not frappe.db.exists("Contact", contact.name):
@@ -333,11 +337,15 @@
return lp_details
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
+ from erpnext.controllers.queries import get_fields
+
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)
+
match_conditions = build_match_conditions("Customer")
match_conditions = "and {}".format(match_conditions) if match_conditions else ""
@@ -345,14 +353,17 @@
filter_conditions = get_filters_cond(doctype, filters, [])
match_conditions += "{}".format(filter_conditions)
- return frappe.db.sql("""select %s from `tabCustomer` where docstatus < 2
- and (%s like %s or customer_name like %s)
- {match_conditions}
+ return frappe.db.sql("""
+ select %s
+ from `tabCustomer`
+ where docstatus < 2
+ and (%s like %s or customer_name like %s)
+ {match_conditions}
order by
- case when name like %s then 0 else 1 end,
- case when customer_name like %s then 0 else 1 end,
- name, customer_name limit %s, %s""".format(match_conditions=match_conditions) %
- (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"),
+ case when name like %s then 0 else 1 end,
+ case when customer_name like %s then 0 else 1 end,
+ name, customer_name limit %s, %s
+ """.format(match_conditions=match_conditions) % (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"),
("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len))
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 45a43c5..705dcb8 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -34,6 +34,15 @@
}
};
})
+
+ frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
+ var row = locals[cdt][cdn];
+ return {
+ filters: {
+ "item": row.item_code
+ }
+ }
+ });
},
refresh: function(frm) {
if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed'
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 6462d3b..b57c4f3 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -282,6 +282,7 @@
"width": "100px"
},
{
+ "fetch_from": "customer.tax_id",
"fieldname": "tax_id",
"fieldtype": "Data",
"label": "Tax Id",
@@ -1196,7 +1197,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-17 12:50:39.640534",
+ "modified": "2020-05-19 21:39:19.486684",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 73f233c..e593499 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -72,6 +72,8 @@
"against_blanket_order",
"blanket_order",
"blanket_order_rate",
+ "manufacturing_section_section",
+ "bom_no",
"planning_section",
"projected_qty",
"actual_qty",
@@ -212,6 +214,7 @@
"fieldtype": "Link",
"label": "UOM",
"options": "UOM",
+ "print_hide": 0,
"reqd": 1
},
{
@@ -764,12 +767,24 @@
"fieldname": "against_blanket_order",
"fieldtype": "Check",
"label": "Against Blanket Order"
- }
+ },
+ {
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "label": "BOM No",
+ "no_copy": 1,
+ "options": "BOM",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "manufacturing_section_section",
+ "fieldtype": "Section Break",
+ "label": "Manufacturing Section"
+ }
],
"idx": 1,
"istable": 1,
- "links": [],
- "modified": "2020-03-05 14:20:28.085117",
+ "modified": "2020-05-15 18:13:43.006493",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.js b/erpnext/selling/page/sales_funnel/sales_funnel.js
index 85c0cd8..e3d0a55 100644
--- a/erpnext/selling/page/sales_funnel/sales_funnel.js
+++ b/erpnext/selling/page/sales_funnel/sales_funnel.js
@@ -90,6 +90,10 @@
get_data(btn) {
var me = this;
+ if (!this.company) {
+ frappe.throw(__("Please Select a Company."));
+ }
+
const method_map = {
"sales_funnel": "erpnext.selling.page.sales_funnel.sales_funnel.get_funnel_data",
"opp_by_lead_source": "erpnext.selling.page.sales_funnel.sales_funnel.get_opp_by_lead_source",
diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.py b/erpnext/selling/page/sales_funnel/sales_funnel.py
index d62e209..dba24ef 100644
--- a/erpnext/selling/page/sales_funnel/sales_funnel.py
+++ b/erpnext/selling/page/sales_funnel/sales_funnel.py
@@ -8,14 +8,23 @@
from erpnext.accounts.report.utils import convert
import pandas as pd
+def validate_filters(from_date, to_date, company):
+ if from_date and to_date and (from_date >= to_date):
+ frappe.throw(_("To Date must be greater than From Date"))
+
+ if not company:
+ frappe.throw(_("Please Select a Company"))
+
@frappe.whitelist()
def get_funnel_data(from_date, to_date, company):
+ validate_filters(from_date, to_date, company)
+
active_leads = frappe.db.sql("""select count(*) from `tabLead`
where (date(`modified`) between %s and %s)
and status != "Do Not Contact" and company=%s""", (from_date, to_date, company))[0][0]
active_leads += frappe.db.sql("""select count(distinct contact.name) from `tabContact` contact
- left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer'
+ left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer'
and (date(contact.modified) between %s and %s) and status != "Passive" """, (from_date, to_date))[0][0]
opportunities = frappe.db.sql("""select count(*) from `tabOpportunity`
@@ -38,6 +47,8 @@
@frappe.whitelist()
def get_opp_by_lead_source(from_date, to_date, company):
+ validate_filters(from_date, to_date, company)
+
opportunities = frappe.get_all("Opportunity", filters=[['status', 'in', ['Open', 'Quotation', 'Replied']], ['company', '=', company], ['transaction_date', 'Between', [from_date, to_date]]], fields=['currency', 'sales_stage', 'opportunity_amount', 'probability', 'source'])
if opportunities:
@@ -68,11 +79,13 @@
@frappe.whitelist()
def get_pipeline_data(from_date, to_date, company):
+ validate_filters(from_date, to_date, company)
+
opportunities = frappe.get_all("Opportunity", filters=[['status', 'in', ['Open', 'Quotation', 'Replied']], ['company', '=', company], ['transaction_date', 'Between', [from_date, to_date]]], fields=['currency', 'sales_stage', 'opportunity_amount', 'probability'])
if opportunities:
default_currency = frappe.get_cached_value('Global Defaults', 'None', 'default_currency')
-
+
cp_opportunities = [dict(x, **{'compound_amount': (convert(x['opportunity_amount'], x['currency'], default_currency, to_date) * x['probability']/100)}) for x in opportunities]
df = pd.DataFrame(cp_opportunities).groupby(['sales_stage'], as_index=True).agg({'compound_amount': 'sum'}).to_dict()
diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js
index a854fa9..d93ffb7 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js
@@ -4,6 +4,14 @@
frappe.query_reports["Customer Acquisition and Loyalty"] = {
"filters": [
{
+ "fieldname": "view_type",
+ "label": __("View Type"),
+ "fieldtype": "Select",
+ "options": ["Monthly", "Territory Wise"],
+ "default": "Monthly",
+ "reqd": 1
+ },
+ {
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
@@ -24,6 +32,13 @@
"fieldtype": "Date",
"default": frappe.defaults.get_user_default("year_end_date"),
"reqd": 1
- },
- ]
-}
+ }
+ ],
+ 'formatter': function(value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+ if (data && data.bold) {
+ value = value.bold();
+ }
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
index aa57665..88bd9c1 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
@@ -2,65 +2,186 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
+import calendar
import frappe
from frappe import _
-from frappe.utils import getdate, cint, cstr
-import calendar
+from frappe.utils import cint, cstr
def execute(filters=None):
- # key yyyy-mm
- new_customers_in = {}
- repeat_customers_in = {}
- customers = []
- company_condition = ""
+ common_columns = [
+ {
+ 'label': _('New Customers'),
+ 'fieldname': 'new_customers',
+ 'fieldtype': 'Int',
+ 'default': 0,
+ 'width': 125
+ },
+ {
+ 'label': _('Repeat Customers'),
+ 'fieldname': 'repeat_customers',
+ 'fieldtype': 'Int',
+ 'default': 0,
+ 'width': 125
+ },
+ {
+ 'label': _('Total'),
+ 'fieldname': 'total',
+ 'fieldtype': 'Int',
+ 'default': 0,
+ 'width': 100
+ },
+ {
+ 'label': _('New Customer Revenue'),
+ 'fieldname': 'new_customer_revenue',
+ 'fieldtype': 'Currency',
+ 'default': 0.0,
+ 'width': 175
+ },
+ {
+ 'label': _('Repeat Customer Revenue'),
+ 'fieldname': 'repeat_customer_revenue',
+ 'fieldtype': 'Currency',
+ 'default': 0.0,
+ 'width': 175
+ },
+ {
+ 'label': _('Total Revenue'),
+ 'fieldname': 'total_revenue',
+ 'fieldtype': 'Currency',
+ 'default': 0.0,
+ 'width': 175
+ }
+ ]
+ if filters.get('view_type') == 'Monthly':
+ return get_data_by_time(filters, common_columns)
+ else:
+ return get_data_by_territory(filters, common_columns)
- if filters.get("company"):
- company_condition = ' and company=%(company)s'
+def get_data_by_time(filters, common_columns):
+ # key yyyy-mm
+ columns = [
+ {
+ 'label': _('Year'),
+ 'fieldname': 'year',
+ 'fieldtype': 'Data',
+ 'width': 100
+ },
+ {
+ 'label': _('Month'),
+ 'fieldname': 'month',
+ 'fieldtype': 'Data',
+ 'width': 100
+ },
+ ]
+ columns += common_columns
- for si in frappe.db.sql("""select posting_date, customer, base_grand_total from `tabSales Invoice`
- where docstatus=1 and posting_date <= %(to_date)s
- {company_condition} order by posting_date""".format(company_condition=company_condition),
- filters, as_dict=1):
+ customers_in = get_customer_stats(filters)
- key = si.posting_date.strftime("%Y-%m")
- if not si.customer in customers:
- new_customers_in.setdefault(key, [0, 0.0])
- new_customers_in[key][0] += 1
- new_customers_in[key][1] += si.base_grand_total
- customers.append(si.customer)
- else:
- repeat_customers_in.setdefault(key, [0, 0.0])
- repeat_customers_in[key][0] += 1
- repeat_customers_in[key][1] += si.base_grand_total
+ # time series
+ from_year, from_month, temp = filters.get('from_date').split('-')
+ to_year, to_month, temp = filters.get('to_date').split('-')
- # time series
- from_year, from_month, temp = filters.get("from_date").split("-")
- to_year, to_month, temp = filters.get("to_date").split("-")
+ from_year, from_month, to_year, to_month = \
+ cint(from_year), cint(from_month), cint(to_year), cint(to_month)
- from_year, from_month, to_year, to_month = \
- cint(from_year), cint(from_month), cint(to_year), cint(to_month)
+ out = []
+ for year in range(from_year, to_year+1):
+ for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
+ key = '{year}-{month:02d}'.format(year=year, month=month)
+ data = customers_in.get(key)
+ new = data['new'] if data else [0, 0.0]
+ repeat = data['repeat'] if data else [0, 0.0]
+ out.append({
+ 'year': cstr(year),
+ 'month': calendar.month_name[month],
+ 'new_customers': new[0],
+ 'repeat_customers': repeat[0],
+ 'total': new[0] + repeat[0],
+ 'new_customer_revenue': new[1],
+ 'repeat_customer_revenue': repeat[1],
+ 'total_revenue': new[1] + repeat[1]
+ })
+ return columns, out
- out = []
- for year in range(from_year, to_year+1):
- for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
- key = "{year}-{month:02d}".format(year=year, month=month)
+def get_data_by_territory(filters, common_columns):
+ columns = [{
+ 'label': 'Territory',
+ 'fieldname': 'territory',
+ 'fieldtype': 'Link',
+ 'options': 'Territory',
+ 'width': 150
+ }]
+ columns += common_columns
- new = new_customers_in.get(key, [0,0.0])
- repeat = repeat_customers_in.get(key, [0,0.0])
+ customers_in = get_customer_stats(filters, tree_view=True)
- out.append([cstr(year), calendar.month_name[month],
- new[0], repeat[0], new[0] + repeat[0],
- new[1], repeat[1], new[1] + repeat[1]])
+ territory_dict = {}
+ for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1):
+ territory_dict.update({
+ t.name: {
+ 'parent': t.parent_territory,
+ 'is_group': t.is_group
+ }
+ })
- return [
- _("Year") + "::100",
- _("Month") + "::100",
- _("New Customers") + ":Int:100",
- _("Repeat Customers") + ":Int:100",
- _("Total") + ":Int:100",
- _("New Customer Revenue") + ":Currency:150",
- _("Repeat Customer Revenue") + ":Currency:150",
- _("Total Revenue") + ":Currency:150"
- ], out
+ depth_map = frappe._dict()
+ for name, info in territory_dict.items():
+ default = depth_map.get(info['parent']) + 1 if info['parent'] else 0
+ depth_map.setdefault(name, default)
+ data = []
+ for name, indent in depth_map.items():
+ condition = customers_in.get(name)
+ new = customers_in[name]['new'] if condition else [0, 0.0]
+ repeat = customers_in[name]['repeat'] if condition else [0, 0.0]
+ temp = {
+ 'territory': name,
+ 'parent_territory': territory_dict[name]['parent'],
+ 'indent': indent,
+ 'new_customers': new[0],
+ 'repeat_customers': repeat[0],
+ 'total': new[0] + repeat[0],
+ 'new_customer_revenue': new[1],
+ 'repeat_customer_revenue': repeat[1],
+ 'total_revenue': new[1] + repeat[1],
+ 'bold': 0 if indent else 1
+ }
+ data.append(temp)
+ loop_data = sorted(data, key=lambda k: k['indent'], reverse=True)
+
+ for ld in loop_data:
+ if ld['parent_territory']:
+ parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0]
+ for key in parent_data.keys():
+ if key not in ['indent', 'territory', 'parent_territory', 'bold']:
+ parent_data[key] += ld[key]
+
+ return columns, data, None, None, None, 1
+
+def get_customer_stats(filters, tree_view=False):
+ """ Calculates number of new and repeated customers. """
+ company_condition = ''
+ if filters.get('company'):
+ company_condition = ' and company=%(company)s'
+
+ customers = []
+ customers_in = {}
+
+ for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice`
+ where docstatus=1 and posting_date <= %(to_date)s and posting_date >= %(from_date)s
+ {company_condition} order by posting_date'''.format(company_condition=company_condition),
+ filters, as_dict=1):
+
+ key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
+ customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
+
+ if not si.customer in customers:
+ customers_in[key]['new'][0] += 1
+ customers_in[key]['new'][1] += si.base_grand_total
+ customers.append(si.customer)
+ else:
+ customers_in[key]['repeat'][0] += 1
+ customers_in[key]['repeat'][1] += si.base_grand_total
+
+ return customers_in
diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.json b/erpnext/selling/report/sales_analytics/sales_analytics.json
index 7193261..bf9edd6 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.json
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.json
@@ -1,31 +1,31 @@
{
- "add_total_row": 0,
- "creation": "2018-09-21 12:46:29.451048",
- "disable_prepared_report": 0,
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2019-05-24 05:37:02.866139",
- "modified_by": "Administrator",
- "module": "Selling",
- "name": "Sales Analytics",
- "owner": "Administrator",
- "prepared_report": 0,
- "ref_doctype": "Sales Order",
- "report_name": "Sales Analytics",
- "report_type": "Script Report",
+ "add_total_row": 0,
+ "creation": "2018-09-21 12:46:29.451048",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-04-30 19:49:02.303320",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Sales Analytics",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Sales Order",
+ "report_name": "Sales Analytics",
+ "report_type": "Script Report",
"roles": [
{
"role": "Stock User"
- },
+ },
{
"role": "Maintenance User"
- },
+ },
{
"role": "Accounts User"
- },
+ },
{
"role": "Sales Manager"
}
diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py
index f1726ab..97d9322 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.py
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.py
@@ -194,6 +194,9 @@
def get_rows(self):
self.data = []
self.get_periodic_data()
+ total_row = {
+ "entity": "Total",
+ }
for entity, period_data in iteritems(self.entity_periodic_data):
row = {
@@ -207,6 +210,9 @@
row[scrub(period)] = amount
total += amount
+ if not total_row.get(scrub(period)): total_row[scrub(period)] = 0
+ total_row[scrub(period)] += amount
+
row["total"] = total
if self.filters.tree_type == "Item":
@@ -214,6 +220,8 @@
self.data.append(row)
+ self.data.append(total_row)
+
def get_rows_by_group(self):
self.get_periodic_data()
out = []
@@ -232,8 +240,10 @@
self.entity_periodic_data.setdefault(d.parent, frappe._dict()).setdefault(period, 0.0)
self.entity_periodic_data[d.parent][period] += amount
total += amount
+
row["total"] = total
out = [row] + out
+
self.data = out
def get_periodic_data(self):
diff --git a/erpnext/selling/report/sales_analytics/test_analytics.py b/erpnext/selling/report/sales_analytics/test_analytics.py
index 4d81a1e..7e8501d 100644
--- a/erpnext/selling/report/sales_analytics/test_analytics.py
+++ b/erpnext/selling/report/sales_analytics/test_analytics.py
@@ -34,6 +34,21 @@
expected_data = [
{
+ 'entity': 'Total',
+ 'apr_2017': 0.0,
+ 'may_2017': 0.0,
+ 'jun_2017': 2000.0,
+ 'jul_2017': 1000.0,
+ 'aug_2017': 0.0,
+ 'sep_2017': 1500.0,
+ 'oct_2017': 1000.0,
+ 'nov_2017': 0.0,
+ 'dec_2017': 0.0,
+ 'jan_2018': 0.0,
+ 'feb_2018': 2000.0,
+ 'mar_2018': 0.0
+ },
+ {
"entity": "_Test Customer 1",
"entity_name": "_Test Customer 1",
"apr_2017": 0.0,
@@ -135,6 +150,21 @@
expected_data = [
{
+ 'entity': 'Total',
+ 'apr_2017': 0.0,
+ 'may_2017': 0.0,
+ 'jun_2017': 20.0,
+ 'jul_2017': 10.0,
+ 'aug_2017': 0.0,
+ 'sep_2017': 15.0,
+ 'oct_2017': 10.0,
+ 'nov_2017': 0.0,
+ 'dec_2017': 0.0,
+ 'jan_2018': 0.0,
+ 'feb_2018': 20.0,
+ 'mar_2018': 0.0
+ },
+ {
"entity": "_Test Customer 1",
"entity_name": "_Test Customer 1",
"apr_2017": 0.0,
diff --git a/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py b/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py
index f2db478..e883500 100644
--- a/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py
+++ b/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py
@@ -20,31 +20,36 @@
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
- "options": "Territory"
+ "options": "Territory",
+ "width": 150
},
{
"label": _("Opportunity Amount"),
"fieldname": "opportunity_amount",
"fieldtype": "Currency",
- "options": currency
+ "options": currency,
+ "width": 150
},
{
"label": _("Quotation Amount"),
"fieldname": "quotation_amount",
"fieldtype": "Currency",
- "options": currency
+ "options": currency,
+ "width": 150
},
{
"label": _("Order Amount"),
"fieldname": "order_amount",
"fieldtype": "Currency",
- "options": currency
+ "options": currency,
+ "width": 150
},
{
"label": _("Billing Amount"),
"fieldname": "billing_amount",
"fieldtype": "Currency",
- "options": currency
+ "options": currency,
+ "width": 150
}
]
@@ -62,8 +67,7 @@
territory_opportunities = list(filter(lambda x: x.territory == territory.name, opportunities))
t_opportunity_names = []
if territory_opportunities:
- t_opportunity_names = [t.name for t in territory_opportunities]
-
+ t_opportunity_names = [t.name for t in territory_opportunities]
territory_quotations = []
if t_opportunity_names and quotations:
territory_quotations = list(filter(lambda x: x.opportunity in t_opportunity_names, quotations))
@@ -76,7 +80,7 @@
list(filter(lambda x: x.quotation in t_quotation_names, sales_orders))
t_order_names = []
if territory_orders:
- t_order_names = [t.name for t in territory_orders]
+ t_order_names = [t.name for t in territory_orders]
territory_invoices = list(filter(lambda x: x.sales_order in t_order_names, sales_invoices)) if t_order_names and sales_invoices else []
@@ -96,12 +100,12 @@
if filters.get('transaction_date'):
conditions = " WHERE transaction_date between {0} and {1}".format(
- frappe.db.escape(filters['transaction_date'][0]),
+ frappe.db.escape(filters['transaction_date'][0]),
frappe.db.escape(filters['transaction_date'][1]))
-
+
if filters.company:
if conditions:
- conditions += " AND"
+ conditions += " AND"
else:
conditions += " WHERE"
conditions += " company = %(company)s"
@@ -115,7 +119,7 @@
def get_quotations(opportunities):
if not opportunities:
return []
-
+
opportunity_names = [o.name for o in opportunities]
return frappe.db.sql("""
@@ -155,5 +159,5 @@
total = 0
for doc in doclist:
total += doc.get(amount_field, 0)
-
+
return total
diff --git a/erpnext/setup/desk_page/getting_started/getting_started.json b/erpnext/setup/desk_page/home/home.json
similarity index 96%
rename from erpnext/setup/desk_page/getting_started/getting_started.json
rename to erpnext/setup/desk_page/home/home.json
index 63d8984..63cd5c5 100644
--- a/erpnext/setup/desk_page/getting_started/getting_started.json
+++ b/erpnext/setup/desk_page/home/home.json
@@ -47,26 +47,20 @@
}
],
"category": "Modules",
- "charts": [
- {
- "chart_name": "Bank Balance",
- "label": "Bank Balance"
- }
- ],
+ "charts": [],
"creation": "2020-01-23 13:46:38.833076",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
- "icon": "",
"idx": 0,
"is_standard": 1,
- "label": "Getting Started",
- "modified": "2020-04-01 11:30:19.763099",
+ "label": "Home",
+ "modified": "2020-05-11 10:20:37.358701",
"modified_by": "Administrator",
"module": "Setup",
- "name": "Getting Started",
+ "name": "Home",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 1,
diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py
index b37cc17..29f6c37 100644
--- a/erpnext/setup/doctype/company/test_company.py
+++ b/erpnext/setup/doctype/company/test_company.py
@@ -47,9 +47,7 @@
frappe.delete_doc("Company", "COA from Existing Company")
def test_coa_based_on_country_template(self):
- countries = ["India", "Brazil", "United Arab Emirates", "Canada", "Germany", "France",
- "Guatemala", "Indonesia", "Italy", "Mexico", "Nicaragua", "Netherlands", "Singapore",
- "Brazil", "Argentina", "Hungary", "Taiwan"]
+ countries = ["Canada", "Germany", "France"]
for country in countries:
templates = get_charts_for_country(country)
diff --git a/erpnext/setup/doctype/territory/territory.py b/erpnext/setup/doctype/territory/territory.py
index 095bd1c..808b538 100644
--- a/erpnext/setup/doctype/territory/territory.py
+++ b/erpnext/setup/doctype/territory/territory.py
@@ -3,8 +3,6 @@
from __future__ import unicode_literals
import frappe
-
-
from frappe.utils import flt
from frappe import _
@@ -14,6 +12,7 @@
nsm_parent_field = 'parent_territory'
def validate(self):
+
for d in self.get('targets') or []:
if not flt(d.target_qty) and not flt(d.target_amount):
frappe.throw(_("Either target qty or target amount is mandatory"))
diff --git a/erpnext/setup/setup_wizard/data/dashboard_charts.py b/erpnext/setup/setup_wizard/data/dashboard_charts.py
new file mode 100644
index 0000000..9ce64eb
--- /dev/null
+++ b/erpnext/setup/setup_wizard/data/dashboard_charts.py
@@ -0,0 +1,151 @@
+from __future__ import unicode_literals
+from frappe import _
+import frappe
+import json
+
+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_default_dashboards():
+ company = frappe.get_doc("Company", get_company_for_dashboards())
+ income_account = company.default_income_account or get_account("Income Account", company.name)
+ expense_account = company.default_expense_account or get_account("Expense Account", company.name)
+ bank_account = company.default_bank_account or get_account("Bank", company.name)
+
+ return {
+ "Dashboards": [
+ {
+ "doctype": "Dashboard",
+ "dashboard_name": "Accounts",
+ "charts": [
+ { "chart": "Outgoing Bills (Sales Invoice)" },
+ { "chart": "Incoming Bills (Purchase Invoice)" },
+ { "chart": "Bank Balance" },
+ { "chart": "Income" },
+ { "chart": "Expenses" },
+ { "chart": "Patient Appointments" }
+ ]
+ },
+ {
+ "doctype": "Dashboard",
+ "dashboard_name": "Project",
+ "charts": [
+ { "chart": "Project Summary", "width": "Full" }
+ ]
+ },
+ ],
+ "Charts": [
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Quarterly",
+ "chart_name": "Income",
+ "timespan": "Last Year",
+ "color": None,
+ "filters_json": json.dumps({"company": company.name, "account": income_account}),
+ "source": "Account Balance Timeline",
+ "chart_type": "Custom",
+ "timeseries": 1,
+ "owner": "Administrator",
+ "type": "Line",
+ "width": "Half"
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Quarterly",
+ "chart_name": "Expenses",
+ "timespan": "Last Year",
+ "color": None,
+ "filters_json": json.dumps({"company": company.name, "account": expense_account}),
+ "source": "Account Balance Timeline",
+ "chart_type": "Custom",
+ "timeseries": 1,
+ "owner": "Administrator",
+ "type": "Line",
+ "width": "Half"
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Quarterly",
+ "chart_name": "Bank Balance",
+ "timespan": "Last Year",
+ "color": "#ffb868",
+ "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"
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Monthly",
+ "chart_name": "Incoming Bills (Purchase Invoice)",
+ "timespan": "Last Year",
+ "color": "#a83333",
+ "value_based_on": "base_grand_total",
+ "filters_json": json.dumps({}),
+ "chart_type": "Sum",
+ "timeseries": 1,
+ "based_on": "posting_date",
+ "owner": "Administrator",
+ "document_type": "Purchase Invoice",
+ "type": "Bar",
+ "width": "Half"
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Monthly",
+ "chart_name": "Outgoing Bills (Sales Invoice)",
+ "timespan": "Last Year",
+ "color": "#7b933d",
+ "value_based_on": "base_grand_total",
+ "filters_json": json.dumps({}),
+ "chart_type": "Sum",
+ "timeseries": 1,
+ "based_on": "posting_date",
+ "owner": "Administrator",
+ "document_type": "Sales Invoice",
+ "type": "Bar",
+ "width": "Half"
+ },
+ {
+ 'doctype': 'Dashboard Chart',
+ 'name': 'Project Summary',
+ 'chart_name': 'Project Summary',
+ 'chart_type': 'Report',
+ 'report_name': 'Project Summary',
+ 'is_public': 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 }}',
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Daily",
+ "chart_name": "Patient Appointments",
+ "timespan": "Last Month",
+ "color": "#77ecca",
+ "filters_json": json.dumps({}),
+ "chart_type": "Count",
+ "timeseries": 1,
+ "based_on": "appointment_datetime",
+ "owner": "Administrator",
+ "document_type": "Patient Appointment",
+ "type": "Line",
+ "width": "Half"
+ }
+ ]
+ }
+
+def get_account(account_type, company):
+ accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company})
+ if accounts:
+ return accounts[0].name
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index e11e1bb..4ac546e 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -541,27 +541,31 @@
return doc.tc_name
@frappe.whitelist(allow_guest=True)
-def apply_coupon_code(applied_code,applied_referral_sales_partner):
+def apply_coupon_code(applied_code, applied_referral_sales_partner):
quotation = True
- if applied_code:
- coupon_list=frappe.get_all('Coupon Code', filters={"docstatus": ("<", "2"), 'coupon_code':applied_code }, fields=['name'])
- if coupon_list:
- coupon_name=coupon_list[0].name
- from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
- validate_coupon_code(coupon_name)
- quotation = _get_cart_quotation()
- quotation.coupon_code=coupon_name
+
+ if not applied_code:
+ frappe.throw(_("Please enter a coupon code"))
+
+ coupon_list = frappe.get_all('Coupon Code', filters={'coupon_code': applied_code})
+ if not coupon_list:
+ frappe.throw(_("Please enter a valid coupon code"))
+
+ coupon_name = coupon_list[0].name
+
+ from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
+ validate_coupon_code(coupon_name)
+ quotation = _get_cart_quotation()
+ quotation.coupon_code = coupon_name
+ quotation.flags.ignore_permissions = True
+ quotation.save()
+
+ if applied_referral_sales_partner:
+ sales_partner_list = frappe.get_all('Sales Partner', filters={'referral_code': applied_referral_sales_partner})
+ if sales_partner_list:
+ sales_partner_name = sales_partner_list[0].name
+ quotation.referral_sales_partner = sales_partner_name
quotation.flags.ignore_permissions = True
quotation.save()
- if applied_referral_sales_partner:
- sales_partner_list=frappe.get_all('Sales Partner', filters={'docstatus': 0, 'referral_code':applied_referral_sales_partner }, fields=['name'])
- if sales_partner_list:
- sales_partner_name=sales_partner_list[0].name
- quotation.referral_sales_partner=sales_partner_name
- quotation.flags.ignore_permissions = True
- quotation.save()
- else:
- frappe.throw(_("Please enter valid coupon code !!"))
- else:
- frappe.throw(_("Please enter coupon code !!"))
+
return quotation
diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py
index a7da09c..21ee335 100644
--- a/erpnext/shopping_cart/product_info.py
+++ b/erpnext/shopping_cart/product_info.py
@@ -10,14 +10,16 @@
from erpnext.utilities.product import get_price, get_qty_in_stock, get_non_stock_item_status
@frappe.whitelist(allow_guest=True)
-def get_product_info_for_website(item_code):
+def get_product_info_for_website(item_code, skip_quotation_creation=False):
"""get product price / stock info for website"""
cart_settings = get_shopping_cart_settings()
if not cart_settings.enabled:
return frappe._dict()
- cart_quotation = _get_cart_quotation()
+ cart_quotation = frappe._dict()
+ if not skip_quotation_creation:
+ cart_quotation = _get_cart_quotation()
price = get_price(
item_code,
@@ -51,7 +53,7 @@
def set_product_info_for_website(item):
"""set product price uom for website"""
- product_info = get_product_info_for_website(item.item_code)
+ product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True)
if product_info:
item.update(product_info)
diff --git a/erpnext/stock/dashboard_chart_source/__init__.py b/erpnext/stock/dashboard_chart_source/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/__init__.py
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/__init__.py b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/__init__.py
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js
new file mode 100644
index 0000000..a413754
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js
@@ -0,0 +1,14 @@
+frappe.provide('frappe.dashboards.chart_sources');
+
+frappe.dashboards.chart_sources["Warehouse wise Stock Value"] = {
+ method: "erpnext.stock.dashboard_chart_source.warehouse_wise_stock_value.warehouse_wise_stock_value.get",
+ filters: [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company")
+ }
+ ]
+};
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.json b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.json
new file mode 100644
index 0000000..6d967c0
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.json
@@ -0,0 +1,13 @@
+{
+ "creation": "2020-05-14 14:27:44.108017",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart Source",
+ "idx": 0,
+ "modified": "2020-05-14 14:27:44.108017",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Warehouse wise Stock Value",
+ "owner": "Administrator",
+ "source_name": "Warehouse wise Stock Value ",
+ "timeseries": 0
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py
new file mode 100644
index 0000000..374a34e
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe, json
+from frappe import _
+from frappe.utils.dashboard import cache_source
+from erpnext.stock.utils import get_stock_value_from_bin
+
+@frappe.whitelist()
+@cache_source
+def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
+ to_date = None, timespan = None, time_interval = None, heatmap_year = None):
+ labels, datapoints = [], []
+ filters = frappe.parse_json(filters)
+
+ warehouse_filters = [['is_group', '=', 0]]
+ if filters and filters.get("company"):
+ warehouse_filters.append(['company', '=', filters.get("company")])
+
+ warehouses = frappe.get_list("Warehouse", fields=['name'], filters=warehouse_filters, order_by='name')
+
+ for wh in warehouses:
+ balance = get_stock_value_from_bin(warehouse=wh.name)
+ wh["balance"] = balance[0][0]
+
+ warehouses = [x for x in warehouses if not (x.get('balance') == None)]
+
+ if not warehouses:
+ return []
+
+ sorted_warehouse_map = sorted(warehouses, key = lambda i: i['balance'], reverse=True)
+
+ if len(sorted_warehouse_map) > 10:
+ sorted_warehouse_map = sorted_warehouse_map[:10]
+
+ for warehouse in sorted_warehouse_map:
+ labels.append(_(warehouse.get("name")))
+ datapoints.append(warehouse.get("balance"))
+
+ return{
+ "labels": labels,
+ "datasets": [{
+ "name": _("Stock Value"),
+ "values": datapoints
+ }],
+ "type": "bar"
+ }
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_fixtures.py b/erpnext/stock/dashboard_fixtures.py
new file mode 100644
index 0000000..0f1fd12
--- /dev/null
+++ b/erpnext/stock/dashboard_fixtures.py
@@ -0,0 +1,175 @@
+# 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": "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():
+ 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():
+ 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/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json
index 38475a6..4506664 100644
--- a/erpnext/stock/desk_page/stock/stock.json
+++ b/erpnext/stock/desk_page/stock/stock.json
@@ -2,8 +2,13 @@
"cards": [
{
"hidden": 0,
+ "label": "Items and Pricing",
+ "links": "[\n {\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shipping Rule\",\n \"name\": \"Shipping Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Alternative\",\n \"name\": \"Item Alternative\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Manufacturer\",\n \"name\": \"Item Manufacturer\",\n \"type\": \"doctype\"\n }\n]"
+ },
+ {
+ "hidden": 0,
"label": "Stock Transactions",
- "links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]"
+ "links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@@ -13,12 +18,7 @@
{
"hidden": 0,
"label": "Settings",
- "links": "[\n {\n \"label\": \"Stock Settings\",\n \"name\": \"Stock Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Warehouse\",\n \"name\": \"Warehouse\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Unit of Measure (UOM)\",\n \"name\": \"UOM\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Brand\",\n \"name\": \"Brand\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Attribute\",\n \"name\": \"Item Attribute\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Variant Settings\",\n \"name\": \"Item Variant Settings\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Items and Pricing",
- "links": "[\n {\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shipping Rule\",\n \"name\": \"Shipping Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Alternative\",\n \"name\": \"Item Alternative\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Manufacturer\",\n \"name\": \"Item Manufacturer\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Variant Settings\",\n \"name\": \"Item Variant Settings\",\n \"type\": \"doctype\"\n }\n]"
+ "links": "[\n {\n \"label\": \"Stock Settings\",\n \"name\": \"Stock Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Warehouse\",\n \"name\": \"Warehouse\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Unit of Measure (UOM)\",\n \"name\": \"UOM\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Variant Settings\",\n \"name\": \"Item Variant Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Brand\",\n \"name\": \"Brand\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Attribute\",\n \"name\": \"Item Attribute\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@@ -41,34 +41,46 @@
"links": "[\n {\n \"dependencies\": [\n \"Material Request\"\n ],\n \"doctype\": \"Material Request\",\n \"is_query_report\": true,\n \"label\": \"Requested Items To Be Transferred\",\n \"name\": \"Requested Items To Be Transferred\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Ledger Entry\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Batch Item Expiry Status\",\n \"name\": \"Batch Item Expiry Status\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Price List\"\n ],\n \"doctype\": \"Price List\",\n \"is_query_report\": true,\n \"label\": \"Item Prices\",\n \"name\": \"Item Prices\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Itemwise Recommended Reorder Level\",\n \"name\": \"Itemwise Recommended Reorder Level\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item Variant Details\",\n \"name\": \"Item Variant Details\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Ledger Entry\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock and Account Value Comparison\",\n \"name\": \"Stock and Account Value Comparison\",\n \"type\": \"report\"\n }\n]"
}
],
+ "cards_label": "Masters & Reports",
"category": "Modules",
- "charts": [],
+ "charts": [
+ {
+ "chart_name": "Warehouse wise Stock Value"
+ }
+ ],
"creation": "2020-03-02 15:43:10.096528",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Stock",
- "modified": "2020-04-01 11:28:51.148421",
+ "modified": "2020-05-19 17:36:08.185652",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",
+ "onboarding": "Stock",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
+ "color": "#cef6d1",
+ "format": "{} available",
"label": "Item",
"link_to": "Item",
+ "stats_filter": "{\n \"disabled\" : 0\n}",
"type": "DocType"
},
{
- "label": "Pricing Rule",
- "link_to": "Pricing Rule",
+ "color": "#ffe8cd",
+ "format": "{} Pending",
+ "label": "Material Request",
+ "link_to": "Material Request",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"Pending\"\n}",
"type": "DocType"
},
{
@@ -77,6 +89,22 @@
"type": "DocType"
},
{
+ "color": "#ffe8cd",
+ "format": "{} to Bill",
+ "label": "Purchase Receipt",
+ "link_to": "Purchase Receipt",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Bill\"\n}",
+ "type": "DocType"
+ },
+ {
+ "color": "#ffe8cd",
+ "format": "{} to Bill",
+ "label": "Delivery Note",
+ "link_to": "Delivery Note",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Bill\"\n}",
+ "type": "DocType"
+ },
+ {
"label": "Stock Ledger",
"link_to": "Stock Ledger",
"type": "Report"
@@ -85,6 +113,12 @@
"label": "Stock Balance",
"link_to": "Stock Balance",
"type": "Report"
+ },
+ {
+ "label": "Stock Dashboard",
+ "link_to": "Stock",
+ "type": "Dashboard"
}
- ]
+ ],
+ "shortcuts_label": "Quick Access"
}
\ 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 9f5dee9..84d2057 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -24,10 +24,10 @@
"return_against",
"customer_po_details",
"po_no",
- "section_break_18",
- "pick_list",
"column_break_17",
"po_date",
+ "section_break_18",
+ "pick_list",
"contact_info",
"shipping_address_name",
"shipping_address",
@@ -296,7 +296,6 @@
},
{
"collapsible": 1,
- "collapsible_depends_on": "po_no",
"fieldname": "customer_po_details",
"fieldtype": "Section Break",
"label": "Customer PO Details"
@@ -304,7 +303,7 @@
{
"allow_on_submit": 1,
"fieldname": "po_no",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"label": "Customer's Purchase Order No",
"no_copy": 1,
"oldfieldname": "po_no",
@@ -318,7 +317,6 @@
"fieldtype": "Column Break"
},
{
- "depends_on": "eval:doc.po_no",
"fieldname": "po_date",
"fieldtype": "Date",
"label": "Customer's Purchase Order Date",
@@ -326,7 +324,6 @@
"oldfieldtype": "Data",
"print_hide": 1,
"print_width": "100px",
- "read_only": 1,
"width": "100px"
},
{
@@ -1256,7 +1253,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-17 12:51:41.288600",
+ "modified": "2020-05-19 17:03:45.880106",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 4cc50bb..7a1c127 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -467,7 +467,7 @@
def set_shopping_cart_data(self, context):
from erpnext.shopping_cart.product_info import get_product_info_for_website
- context.shopping_cart = get_product_info_for_website(self.name)
+ context.shopping_cart = get_product_info_for_website(self.name, skip_quotation_creation=True)
def add_default_uom_in_conversion_factor_table(self):
uom_conv_list = [d.uom for d in self.get("uoms")]
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index 957c415..8e39eb5 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -69,3 +69,10 @@
self.reference = self.customer
if self.buying:
self.reference = self.supplier
+
+ if self.selling and not self.buying:
+ # if only selling then remove supplier
+ self.supplier = None
+ if self.buying and not self.selling:
+ # if only buying then remove customer
+ self.customer = None
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index db8bffd..3562181 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -30,7 +30,16 @@
return {
filters: {'company': doc.company}
};
- })
+ });
+
+ frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
+ var row = locals[cdt][cdn];
+ return {
+ filters: {
+ "item": row.item_code
+ }
+ }
+ });
},
onload: function(frm) {
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index df140ff..32bd4a0 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -53,6 +53,8 @@
"dimension_col_break",
"cost_center",
"section_break_37",
+ "bom_no",
+ "section_break_46",
"page_break"
],
"fields": [
@@ -371,8 +373,10 @@
"label": "Image"
},
{
+ "depends_on": "eval:parent.material_request_type == \"Manufacture\"",
"fieldname": "section_break_37",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Manufacturing"
},
{
"fieldname": "received_qty",
@@ -428,12 +432,24 @@
"fieldtype": "Link",
"label": "Source Warehouse (Material Transfer)",
"options": "Warehouse"
+ },
+ {
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "label": "BOM No",
+ "no_copy": 1,
+ "options": "BOM",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "section_break_46",
+ "fieldtype": "Section Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-05-01 09:00:00.992835",
+ "modified": "2020-05-15 09:00:00.992835",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
index a9f3cd0..c951066 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "naming_series:",
"creation": "2013-04-30 13:13:03",
"doctype": "DocType",
@@ -8,6 +9,7 @@
"field_order": [
"naming_series",
"report_date",
+ "status",
"column_break_4",
"inspection_type",
"reference_type",
@@ -20,17 +22,16 @@
"column_break1",
"item_name",
"description",
- "status",
+ "bom_no",
+ "specification_details",
+ "quality_inspection_template",
+ "readings",
"section_break_14",
"inspected_by",
"verified_by",
- "bom_no",
"column_break_17",
"remarks",
- "amended_from",
- "specification_details",
- "quality_inspection_template",
- "readings"
+ "amended_from"
],
"fields": [
{
@@ -231,7 +232,8 @@
"icon": "fa fa-search",
"idx": 1,
"is_submittable": 1,
- "modified": "2019-07-12 12:07:23.153698",
+ "links": [],
+ "modified": "2020-04-26 17:50:25.068222",
"modified_by": "Administrator",
"module": "Stock",
"name": "Quality Inspection",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index ddf4ec0..18d6853 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -181,7 +181,7 @@
stock_items = self.get_stock_items()
serialized_items = self.get_serialized_items()
for item in self.get("items"):
- if item.qty and item.qty < 0:
+ if flt(item.qty) and flt(item.qty) < 0:
frappe.throw(_("Row {0}: The item {1}, quantity must be positive number")
.format(item.idx, frappe.bold(item.item_code)))
@@ -363,6 +363,9 @@
+ self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError)
def set_incoming_rate(self):
+ if self.purpose == "Repack":
+ self.set_basic_rate_for_finished_goods()
+
for d in self.items:
if d.s_warehouse:
args = self.get_args_for_incoming_rate(d)
@@ -470,25 +473,36 @@
"qty": item.s_warehouse and -1*flt(item.transfer_qty) or flt(item.transfer_qty),
"serial_no": item.serial_no,
"voucher_type": self.doctype,
- "voucher_no": item.name,
+ "voucher_no": self.name,
"company": self.company,
"allow_zero_valuation": item.allow_zero_valuation_rate,
})
- def set_basic_rate_for_finished_goods(self, raw_material_cost, scrap_material_cost):
+ def set_basic_rate_for_finished_goods(self, raw_material_cost=0, scrap_material_cost=0):
+ total_fg_qty = 0
+ if not raw_material_cost and self.get("items"):
+ raw_material_cost = sum([flt(row.basic_amount) for row in self.items
+ if row.s_warehouse and not row.t_warehouse])
+
+ total_fg_qty = sum([flt(row.qty) for row in self.items
+ if row.t_warehouse and not row.s_warehouse])
+
if self.purpose in ["Manufacture", "Repack"]:
for d in self.get("items"):
if (d.transfer_qty and (d.bom_no or d.t_warehouse)
and (getattr(self, "pro_doc", frappe._dict()).scrap_warehouse != d.t_warehouse)):
- if self.work_order \
- and frappe.db.get_single_value("Manufacturing Settings", "material_consumption"):
+ if (self.work_order and self.purpose == "Manufacture"
+ and frappe.db.get_single_value("Manufacturing Settings", "material_consumption")):
bom_items = self.get_bom_raw_materials(d.transfer_qty)
raw_material_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()])
- if raw_material_cost:
+ if raw_material_cost and self.purpose == "Manufacture":
d.basic_rate = flt((raw_material_cost - scrap_material_cost) / flt(d.transfer_qty), d.precision("basic_rate"))
d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount"))
+ elif self.purpose == "Repack" and total_fg_qty:
+ d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty)
+ d.basic_amount = d.basic_rate * d.qty
def distribute_additional_costs(self):
if self.purpose == "Material Issue":
@@ -718,11 +732,15 @@
pro_doc = frappe.get_doc("Work Order", self.work_order)
_validate_work_order(pro_doc)
pro_doc.run_method("update_status")
+
if self.fg_completed_qty:
pro_doc.run_method("update_work_order_qty")
if self.purpose == "Manufacture":
pro_doc.run_method("update_planned_qty")
+ if not pro_doc.operations:
+ pro_doc.set_actual_dates()
+
def get_item_details(self, args=None, for_update=False):
item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group,
i.has_batch_no, i.sample_quantity, i.has_serial_no,
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js
index cc0e2cf..6f97572 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.js
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.js
@@ -15,3 +15,37 @@
frm.set_query("sample_retention_warehouse", filters);
}
});
+
+frappe.tour['Stock Settings'] = [
+ {
+ fieldname: "item_naming_by",
+ title: __("Item Naming By"),
+ description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a set Naming Series choose the 'Naming Series' option.")
+ },
+ {
+ fieldname: "default_warehouse",
+ title: __("Default Warehouse"),
+ description: __("Set a Default Warehouse for Inventory Transactions. This will be fetched into the Default Warehouse in the Item master.")
+ },
+ {
+ fieldname: "allow_negative_stock",
+ title: __("Allow Negative Stock"),
+ description: __("This will allow stock items to be displayed in negative values. Using this option depends on your use case. With this option unchecked, the system warns before obstructing a transaction that is causing negative stock.")
+
+ },
+ {
+ fieldname: "valuation_method",
+ title: __("Valuation Method"),
+ description: __("Choose between FIFO and Moving Average Valuation Methods. Click ") + "<a href='https://docs.erpnext.com/docs/user/manual/en/stock/articles/item-valuation-fifo-and-moving-average'>here</a>" + __(" to know more about them.")
+ },
+ {
+ fieldname: "show_barcode_field",
+ title: __("Show Barcode Field"),
+ description: __("Show 'Scan Barcode' field above every child table to insert Items with ease.")
+ },
+ {
+ fieldname: "automatically_set_serial_nos_based_on_fifo",
+ title: __("Automatically Set Serial Nos based on FIFO"),
+ description: __("Serial numbers for stock will be set automatically based on the Items entered based on first in first out in transactions like Purchase/Sales Invoices, Delivery Notes, etc.")
+ }
+];
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index d50712a..0ed3b27 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -305,7 +305,8 @@
"weight_uom":item.weight_uom,
"last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0,
"transaction_date": args.get("transaction_date"),
- "against_blanket_order": args.get("against_blanket_order")
+ "against_blanket_order": args.get("against_blanket_order"),
+ "bom_no": item.get("default_bom")
})
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
@@ -630,7 +631,7 @@
elif args.get("supplier"):
conditions += " and supplier=%(supplier)s"
else:
- conditions += " and (customer is null or customer = '') and (supplier is null or supplier = '')"
+ conditions += "and (customer is null or customer = '') and (supplier is null or supplier = '')"
if args.get('transaction_date'):
conditions += """ and %(transaction_date)s between
diff --git a/erpnext/stock/module_onboarding/stock/stock.json b/erpnext/stock/module_onboarding/stock/stock.json
new file mode 100644
index 0000000..de24575
--- /dev/null
+++ b/erpnext/stock/module_onboarding/stock/stock.json
@@ -0,0 +1,54 @@
+{
+ "allow_roles": [
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Stock Manager"
+ },
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-15 03:18:44.400108",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/stock",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-19 19:03:23.602423",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Setup your Warehouse"
+ },
+ {
+ "step": "Create a Product"
+ },
+ {
+ "step": "Introduction to Stock Entry"
+ },
+ {
+ "step": "Create a Stock Entry"
+ },
+ {
+ "step": "Create a Supplier"
+ },
+ {
+ "step": "Create a Purchase Receipt"
+ },
+ {
+ "step": "Stock Settings"
+ }
+ ],
+ "subtitle": "Inventory, Warehouses, Analysis and more.",
+ "success_message": "The Stock Module is all set up!",
+ "title": "Let's Setup the Stock Module.",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/buying_settings/buying_settings.json b/erpnext/stock/onboarding_step/buying_settings/buying_settings.json
new file mode 100644
index 0000000..a788ccd
--- /dev/null
+++ b/erpnext/stock/onboarding_step/buying_settings/buying_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Update Settings",
+ "creation": "2020-05-06 15:53:44.667414",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "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": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_product/create_a_product.json b/erpnext/stock/onboarding_step/create_a_product/create_a_product.json
new file mode 100644
index 0000000..d2068e1
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_product/create_a_product.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 18:16:06.624554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-12 18:30:02.489949",
+ "modified_by": "Administrator",
+ "name": "Create a Product",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create a Product",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json b/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json
new file mode 100644
index 0000000..b7811a4
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 18:59:13.266713",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 18:59:13.266713",
+ "modified_by": "Administrator",
+ "name": "Create a Purchase Receipt",
+ "owner": "Administrator",
+ "reference_document": "Purchase Receipt",
+ "show_full_form": 1,
+ "title": "Create a Purchase Receipt",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json b/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json
new file mode 100644
index 0000000..2b83f65
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-15 03:20:16.277043",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-15 03:30:58.047696",
+ "modified_by": "Administrator",
+ "name": "Create a Stock Entry",
+ "owner": "Administrator",
+ "reference_document": "Stock Entry",
+ "show_full_form": 1,
+ "title": "Create a Stock Entry",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json
new file mode 100644
index 0000000..7a64224
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 22:09:10.043554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 22:09:10.043554",
+ "modified_by": "Administrator",
+ "name": "Create a Supplier",
+ "owner": "Administrator",
+ "reference_document": "Supplier",
+ "show_full_form": 0,
+ "title": "Create a Supplier",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json
new file mode 100644
index 0000000..447611f
--- /dev/null
+++ b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json
@@ -0,0 +1,19 @@
+{
+ "action": "Watch Video",
+ "creation": "2020-05-15 02:47:17.958806",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 18:55:41.457289",
+ "modified_by": "Administrator",
+ "name": "Introduction to Stock Entry",
+ "owner": "Administrator",
+ "show_full_form": 0,
+ "title": "Introduction to the multi-purpose stock transaction",
+ "validate_action": 1,
+ "video_url": "https://www.youtube.com/watch?v=Njt107hlY3I"
+}
\ 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
new file mode 100644
index 0000000..557c905
--- /dev/null
+++ b/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
@@ -0,0 +1,20 @@
+{
+ "action": "Go to Page",
+ "creation": "2020-05-19 18:54:19.383397",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 18:54:19.383397",
+ "modified_by": "Administrator",
+ "name": "Setup your Warehouse",
+ "owner": "Administrator",
+ "path": "Tree/Warehouse",
+ "reference_document": "Warehouse",
+ "show_full_form": 0,
+ "title": "Setup your Warehouse",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/stock_settings/stock_settings.json b/erpnext/stock/onboarding_step/stock_settings/stock_settings.json
new file mode 100644
index 0000000..7591bff
--- /dev/null
+++ b/erpnext/stock/onboarding_step/stock_settings/stock_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-15 02:53:57.209967",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-15 03:55:15.444151",
+ "modified_by": "Administrator",
+ "name": "Stock Settings",
+ "owner": "Administrator",
+ "reference_document": "Stock Settings",
+ "show_full_form": 0,
+ "title": "Explore Stock Settings",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
index 27cf6b6..5a931e7 100644
--- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
+++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from erpnext.controllers.trends import get_columns,get_data
def execute(filters=None):
@@ -10,5 +11,39 @@
data = []
conditions = get_columns(filters, "Delivery Note")
data = get_data(filters, conditions)
-
- return conditions["columns"], data
\ No newline at end of file
+
+ chart_data = get_chart_data(data, filters)
+
+ return conditions["columns"], data, None, chart_data
+
+def get_chart_data(data, filters):
+ if not data:
+ return []
+
+ labels, datapoints = [], []
+
+ if filters.get("group_by"):
+ # consider only consolidated row
+ data = [row for row in data if row[0]]
+
+ if len(data) > 10:
+ # get top 10 if data too long
+ data = sorted(data, key = lambda i: i[-1],reverse=True)
+ data = data[:10]
+
+ for row in data:
+ labels.append(row[0])
+ datapoints.append(row[-1])
+
+ return {
+ "data": {
+ "labels" : labels,
+ "datasets" : [
+ {
+ "name": _("Total Delivered Amount"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type" : "bar"
+ }
\ No newline at end of file
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.js b/erpnext/stock/report/item_shortage_report/item_shortage_report.js
new file mode 100644
index 0000000..ca42a33
--- /dev/null
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Item Shortage Report"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_default("company")
+ },
+ {
+ "fieldname": "warehouse",
+ "label": __("Warehouse"),
+ "fieldtype": "MultiSelectList",
+ "width": "100",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Warehouse', txt);
+ }
+ }
+ ]
+};
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.json b/erpnext/stock/report/item_shortage_report/item_shortage_report.json
index 577a853..17285c0 100644
--- a/erpnext/stock/report/item_shortage_report/item_shortage_report.json
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.json
@@ -1,29 +1,30 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2013-08-20 13:43:30",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "json": "{\"add_total_row\": 0, \"sort_by\": \"Bin.projected_qty\", \"sort_order\": \"asc\", \"sort_by_next\": \"\", \"filters\": [[\"Bin\", \"projected_qty\", \"<\", \"0\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"warehouse\", \"Bin\"], [\"item_code\", \"Bin\"], [\"actual_qty\", \"Bin\"], [\"ordered_qty\", \"Bin\"], [\"planned_qty\", \"Bin\"], [\"reserved_qty\", \"Bin\"], [\"projected_qty\", \"Bin\"]]}",
- "modified": "2017-02-24 20:00:46.439935",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Item Shortage Report",
- "owner": "Administrator",
- "query": "SELECT bin.warehouse as \"Warehouse:Link/Warehouse:150\",\n\tbin.item_code as \"Item Code:Link/Item:100\",\n\tbin.actual_qty as \"Actual Quantity:Float:120\",\n\tbin.ordered_qty as \"Ordered Quantity:Float:120\",\n\tbin.planned_qty as \"Planned Quantity:Float:120\",\n\tbin.reserved_qty as \"Reserved Quantity:Float:120\",\n\tbin.projected_qty as \"Project Quantity:Float:120\",\n\titem.item_name as \"Item Name:Data:150\",\n\titem.description as \"Description::200\"\nFROM tabBin as bin\nINNER JOIN tabItem as item\nON bin.item_code=item.name\nWHERE bin.projected_qty<0\nORDER BY bin.projected_qty;",
- "ref_doctype": "Bin",
- "report_name": "Item Shortage Report",
- "report_type": "Query Report",
+ "add_total_row": 0,
+ "creation": "2013-08-20 13:43:30",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 3,
+ "is_standard": "Yes",
+ "json": "{\"add_total_row\": 0, \"sort_by\": \"Bin.projected_qty\", \"sort_order\": \"asc\", \"sort_by_next\": \"\", \"filters\": [[\"Bin\", \"projected_qty\", \"<\", \"0\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"warehouse\", \"Bin\"], [\"item_code\", \"Bin\"], [\"actual_qty\", \"Bin\"], [\"ordered_qty\", \"Bin\"], [\"planned_qty\", \"Bin\"], [\"reserved_qty\", \"Bin\"], [\"projected_qty\", \"Bin\"]]}",
+ "modified": "2020-05-14 12:32:07.158991",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Item Shortage Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "",
+ "ref_doctype": "Bin",
+ "report_name": "Item Shortage Report",
+ "report_type": "Script Report",
"roles": [
{
"role": "Sales User"
- },
+ },
{
"role": "Purchase User"
- },
+ },
{
"role": "Stock User"
}
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.py b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
new file mode 100644
index 0000000..086d833
--- /dev/null
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
@@ -0,0 +1,162 @@
+# 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):
+ columns = get_columns()
+ conditions = get_conditions(filters)
+ data = get_data(conditions, filters)
+
+ if not data:
+ return [], [], None, []
+
+ chart_data = get_chart_data(data)
+
+ return columns, data, None, chart_data
+
+def get_conditions(filters):
+ conditions = ""
+
+ if filters.get("warehouse"):
+ conditions += "AND warehouse in %(warehouse)s"
+ if filters.get("company"):
+ conditions += "AND company = %(company)s"
+
+ return conditions
+
+def get_data(conditions, filters):
+ data = frappe.db.sql("""
+ SELECT
+ bin.warehouse,
+ bin.item_code,
+ bin.actual_qty ,
+ bin.ordered_qty ,
+ bin.planned_qty ,
+ bin.reserved_qty ,
+ bin.reserved_qty_for_production,
+ bin.projected_qty ,
+ warehouse.company,
+ item.item_name ,
+ item.description
+ FROM
+ `tabBin` bin,
+ `tabWarehouse` warehouse,
+ `tabItem` item
+ WHERE
+ bin.projected_qty<0
+ AND warehouse.name = bin.warehouse
+ AND bin.item_code=item.name
+ {0}
+ ORDER BY bin.projected_qty;""".format(conditions), filters, as_dict=1)
+
+ return data
+
+def get_chart_data(data):
+ labels, datapoints = [], []
+
+ for row in data:
+ labels.append(row.get("item_code"))
+ datapoints.append(row.get("projected_qty"))
+
+ if len(data) > 10:
+ labels = labels[:10]
+ datapoints = datapoints[:10]
+
+ return {
+ "data": {
+ "labels": labels,
+ "datasets":[
+ {
+ "name": _("Projected Qty"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type": "bar"
+ }
+
+def get_columns():
+ columns = [
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 150
+ },
+ {
+ "label": _("Item"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 150
+ },
+ {
+ "label": _("Actual Quantity"),
+ "fieldname": "actual_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Ordered Quantity"),
+ "fieldname": "ordered_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Planned Quantity"),
+ "fieldname": "planned_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Reserved Quantity"),
+ "fieldname": "reserved_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Reserved Quantity for Production"),
+ "fieldname": "reserved_qty_for_production",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Projected Quantity"),
+ "fieldname": "projected_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 120
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": _("Description"),
+ "fieldname": "description",
+ "fieldtype": "Data",
+ "width": 120
+ }
+ ]
+
+ return columns
+
+
diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
index 0e58920..8227f15 100644
--- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
+++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from erpnext.controllers.trends import get_columns,get_data
def execute(filters=None):
@@ -11,4 +12,40 @@
conditions = get_columns(filters, "Purchase Receipt")
data = get_data(filters, conditions)
- return conditions["columns"], data
\ No newline at end of file
+ chart_data = get_chart_data(data, filters)
+
+ return conditions["columns"], data, None, chart_data
+
+def get_chart_data(data, filters):
+ if not data:
+ return []
+
+ labels, datapoints = [], []
+
+ if filters.get("group_by"):
+ # consider only consolidated row
+ data = [row for row in data if row[0]]
+
+ data = sorted(data, key = lambda i: i[-1], reverse=True)
+
+ if len(data) > 10:
+ # get top 10 if data too long
+ data = data[:10]
+
+ for row in data:
+ labels.append(row[0])
+ datapoints.append(row[-1])
+
+ return {
+ "data": {
+ "labels" : labels,
+ "datasets" : [
+ {
+ "name": _("Total Received Amount"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type" : "bar",
+ "colors":["#5e64ff"]
+ }
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index 803a5c8..af99780 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -37,7 +37,9 @@
data.append(row)
- return columns, data
+ chart_data = get_chart_data(data, filters)
+
+ return columns, data, None, chart_data
def get_average_age(fifo_queue, to_date):
batch_age = age_qty = total_qty = 0.0
@@ -51,7 +53,7 @@
age_qty += batch_age * 1
total_qty += 1
- return (age_qty / total_qty) if total_qty else 0.0
+ return flt(age_qty / total_qty, 2) if total_qty else 0.0
def get_columns(filters):
columns = [
@@ -230,3 +232,34 @@
where wh.lft >= {0} and rgt <= {1})""".format(lft, rgt))
return "and {}".format(" and ".join(conditions)) if conditions else ""
+
+def get_chart_data(data, filters):
+ if not data:
+ return []
+
+ labels, datapoints = [], []
+
+ if filters.get("show_warehouse_wise_stock"):
+ return {}
+
+ data.sort(key = lambda row: row[6], reverse=True)
+
+ if len(data) > 10:
+ data = data[:10]
+
+ for row in data:
+ labels.append(row[0])
+ datapoints.append(row[6])
+
+ return {
+ "data" : {
+ "labels": labels,
+ "datasets": [
+ {
+ "name": _("Average Age"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type" : "bar"
+ }
\ No newline at end of file
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index b4cb8ca..e1b3730 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -548,7 +548,16 @@
if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \
and cint(erpnext.is_perpetual_inventory_enabled(company)):
frappe.local.message_log = []
- frappe.throw(_("Valuation rate not found for the Item {0}, which is required to do accounting entries for {1} {2}. If the item is transacting as a zero valuation rate item in the {1}, please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting / cancelling this entry.")
- .format(item_code, voucher_type, voucher_no))
+ form_link = frappe.utils.get_link_to_form("Item", item_code)
+
+ message = _("Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.").format(form_link, voucher_type, voucher_no)
+ message += "<br><br>" + _(" Here are the options to proceed:")
+ solutions = "<li>" + _("If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.").format(voucher_type) + "</li>"
+ solutions += "<li>" + _("If not, you can Cancel / Submit this entry ") + _("{0}").format(frappe.bold("after")) + _(" performing either one below:") + "</li>"
+ sub_solutions = "<ul><li>" + _("Create an incoming stock transaction for the Item.") + "</li>"
+ sub_solutions += "<li>" + _("Mention Valuation Rate in the Item master.") + "</li></ul>"
+ msg = message + solutions + sub_solutions + "</li>"
+
+ frappe.throw(msg=msg, title=_("Valuation Rate Missing"))
return valuation_rate
diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json
index 0f15e47..1df9fb7 100644
--- a/erpnext/support/web_form/issues/issues.json
+++ b/erpnext/support/web_form/issues/issues.json
@@ -18,7 +18,7 @@
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
- "modified": "2020-03-06 05:24:05.749664",
+ "modified": "2020-05-19 13:01:10.729088",
"modified_by": "Administrator",
"module": "Support",
"name": "issues",
@@ -76,7 +76,7 @@
{
"allow_read_on_all_link_options": 0,
"fieldname": "description",
- "fieldtype": "Text",
+ "fieldtype": "Text Editor",
"hidden": 0,
"label": "Description",
"max_length": 0,
diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py
index ce0f47d..df715ab 100644
--- a/erpnext/tests/test_woocommerce.py
+++ b/erpnext/tests/test_woocommerce.py
@@ -24,7 +24,7 @@
woo_settings.creation_user = "Administrator"
woo_settings.save(ignore_permissions=True)
- def test_sales_order_for_woocommerece(self):
+ def test_sales_order_for_woocommerce(self):
frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"Woocommerce","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]}
order()