Merge branch 'develop' into project-refresh

This commit is contained in:
Shivam Mishra 2020-05-19 11:54:50 +05:30 committed by GitHub
commit afeed27f8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
116 changed files with 2513 additions and 1854 deletions

View File

@ -1,6 +1,5 @@
dist: trusty
language: python language: python
dist: trusty
git: git:
depth: 1 depth: 1
@ -14,21 +13,10 @@ addons:
jobs: jobs:
include: 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" - name: "Python 3.6 Server Side Test"
python: 3.6 python: 3.6
script: bench --site test_site run-tests --app erpnext --coverage 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" - name: "Python 3.6 Patch Test"
python: 3.6 python: 3.6
before_script: before_script:
@ -40,8 +28,7 @@ install:
- cd ~ - cd ~
- nvm install 10 - nvm install 10
- git clone https://github.com/frappe/bench --depth 1 - pip install frappe-bench
- pip install -e ./bench
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1 - git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench - bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench

View File

View File

@ -14,7 +14,7 @@ from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist() @frappe.whitelist()
@cache_source @cache_source
def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None, 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: if chart_name:
chart = frappe.get_doc('Dashboard Chart', chart_name) chart = frappe.get_doc('Dashboard Chart', chart_name)
else: else:

View File

@ -1,127 +1,264 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from erpnext import get_default_company
import frappe import frappe
import json import json
from frappe.utils import nowdate, add_months, get_date_str
from frappe import _
from erpnext.accounts.utils import get_fiscal_year, get_account_name
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(): def get_data():
data = frappe._dict({ return frappe._dict({
"dashboards": [], "dashboards": get_dashboards(),
"charts": [] "charts": get_charts(),
"number_cards": get_number_cards()
}) })
company = get_company_for_dashboards()
if company:
company_doc = frappe.get_doc("Company", company)
data.dashboards = get_dashboards()
data.charts = get_charts(company_doc)
return data
def get_dashboards(): def get_dashboards():
return [{ return [{
"name": "Accounts", "name": "Accounts Dashboard",
"dashboard_name": "Accounts", "dashboard_name": "Accounts Dashboard",
"doctype": "Dashboard",
"charts": [ "charts": [
{ "chart": "Outgoing Bills (Sales Invoice)" }, { "chart": "Profit and Loss" , "width": "Full"},
{ "chart": "Incoming Bills (Purchase Invoice)" }, { "chart": "Incoming Bills (Purchase Invoice)", "width": "Half"},
{ "chart": "Bank Balance" }, { "chart": "Outgoing Bills (Sales Invoice)", "width": "Half"},
{ "chart": "Income" }, { "chart": "Accounts Receivable Ageing", "width": "Half"},
{ "chart": "Expenses" } { "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): def get_charts():
income_account = company.default_income_account or get_account("Income Account", company.name) company = frappe.get_doc("Company", get_company_for_dashboards())
expense_account = company.default_expense_account or get_account("Expense Account", company.name) bank_account = company.default_bank_account or get_account_name("Bank", company=company.name)
bank_account = company.default_bank_account or get_account("Bank", company.name) fiscal_year = get_fiscal_year(date=nowdate())
default_cost_center = company.cost_center
return [ return [
{ {
"doctype": "Dashboard Chart", "doctype": "Dashboard Charts",
"time_interval": "Quarterly", "name": "Profit and Loss",
"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", "owner": "Administrator",
"type": "Line" "report_name": "Profit and Loss Statement",
}, "filters_json": json.dumps({
{ "company": company.name,
"doctype": "Dashboard Chart", "filter_based_on": "Date Range",
"time_interval": "Quarterly", "period_start_date": get_date_str(fiscal_year[1]),
"name": "Expenses", "period_end_date": get_date_str(fiscal_year[2]),
"chart_name": "Expenses", "periodicity": "Monthly",
"timespan": "Last Year", "include_default_book_entries": 1
"color": None, }),
"filters_json": json.dumps({"company": company.name, "account": expense_account}), "type": "Bar",
"source": "Account Balance Timeline", 'timeseries': 0,
"chart_type": "Custom", "chart_type": "Report",
"timeseries": 1, "chart_name": _("Profit and Loss"),
"owner": "Administrator", "is_custom": 1,
"type": "Line" "is_public": 1
},
{
"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", "doctype": "Dashboard Chart",
"time_interval": "Monthly", "time_interval": "Monthly",
"name": "Incoming Bills (Purchase Invoice)", "name": "Incoming Bills (Purchase Invoice)",
"chart_name": "Incoming Bills (Purchase Invoice)", "chart_name": _("Incoming Bills (Purchase Invoice)"),
"timespan": "Last Year", "timespan": "Last Year",
"color": "#a83333", "color": "#a83333",
"value_based_on": "base_grand_total", "value_based_on": "base_net_total",
"filters_json": json.dumps({}), "filters_json": json.dumps({"docstatus": 1}),
"chart_type": "Sum", "chart_type": "Sum",
"timeseries": 1, "timeseries": 1,
"based_on": "posting_date", "based_on": "posting_date",
"owner": "Administrator", "owner": "Administrator",
"document_type": "Purchase Invoice", "document_type": "Purchase Invoice",
"type": "Bar" "type": "Bar",
"width": "Half",
"is_public": 1
}, },
{ {
"doctype": "Dashboard Chart", "doctype": "Dashboard Chart",
"time_interval": "Monthly",
"name": "Outgoing Bills (Sales Invoice)", "name": "Outgoing Bills (Sales Invoice)",
"chart_name": "Outgoing Bills (Sales Invoice)", "time_interval": "Monthly",
"chart_name": _("Outgoing Bills (Sales Invoice)"),
"timespan": "Last Year", "timespan": "Last Year",
"color": "#7b933d", "color": "#7b933d",
"value_based_on": "base_grand_total", "value_based_on": "base_net_total",
"filters_json": json.dumps({}), "filters_json": json.dumps({"docstatus": 1}),
"chart_type": "Sum", "chart_type": "Sum",
"timeseries": 1, "timeseries": 1,
"based_on": "posting_date", "based_on": "posting_date",
"owner": "Administrator", "owner": "Administrator",
"document_type": "Sales Invoice", "document_type": "Sales Invoice",
"type": "Bar" "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_account(account_type, company): def get_number_cards():
accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company}) fiscal_year = get_fiscal_year(date=nowdate())
if accounts: year_start_date = get_date_str(fiscal_year[1])
return accounts[0].name year_end_date = get_date_str(fiscal_year[2])
return [
def get_company_for_dashboards(): {
company = get_default_company() "doctype": "Number Card",
if not company: "document_type": "Payment Entry",
company_list = frappe.get_list("Company") "name": "Total Incoming Payment",
if company_list: "filters_json": json.dumps([
company = company_list[0].name ['Payment Entry', 'docstatus', '=', 1],
return company ['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"
}
]

View File

@ -185,7 +185,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
total_days, total_booking_days, account_currency) total_days, total_booking_days, account_currency)
make_gl_entries(doc, credit_account, debit_account, against, 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 # 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: if frappe.flags.deferred_accounting_error:
@ -222,7 +222,7 @@ def process_deferred_accounting(posting_date=today()):
doc.submit() doc.submit()
def make_gl_entries(doc, credit_account, debit_account, against, 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 # GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
@ -236,12 +236,12 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"credit": base_amount, "credit": base_amount,
"credit_in_account_currency": amount, "credit_in_account_currency": amount,
"cost_center": cost_center, "cost_center": cost_center,
"voucher_detail_no": voucher_detail_no, "voucher_detail_no": item.name,
'posting_date': posting_date, 'posting_date': posting_date,
'project': project, 'project': project,
'against_voucher_type': 'Process Deferred Accounting', 'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process 'against_voucher': deferred_process
}, account_currency) }, account_currency, item=item)
) )
# GL Entry to debit the amount from the expense # GL Entry to debit the amount from the expense
gl_entries.append( gl_entries.append(
@ -251,12 +251,12 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"debit": base_amount, "debit": base_amount,
"debit_in_account_currency": amount, "debit_in_account_currency": amount,
"cost_center": cost_center, "cost_center": cost_center,
"voucher_detail_no": voucher_detail_no, "voucher_detail_no": item.name,
'posting_date': posting_date, 'posting_date': posting_date,
'project': project, 'project': project,
'against_voucher_type': 'Process Deferred Accounting', 'against_voucher_type': 'Process Deferred Accounting',
'against_voucher': deferred_process 'against_voucher': deferred_process
}, account_currency) }, account_currency, item=item)
) )
if gl_entries: if gl_entries:

View File

@ -45,11 +45,6 @@
"label": "Bank Statement", "label": "Bank Statement",
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]" "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
}, },
{
"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, "hidden": 0,
"label": "Subscription Management", "label": "Subscription Management",
@ -89,8 +84,8 @@
"category": "Modules", "category": "Modules",
"charts": [ "charts": [
{ {
"chart_name": "Bank Balance", "chart_name": "Profit and Loss",
"label": "Bank Balance" "label": "Profit and Loss"
} }
], ],
"creation": "2020-03-02 15:41:59.515192", "creation": "2020-03-02 15:41:59.515192",
@ -99,23 +94,38 @@
"docstatus": 0, "docstatus": 0,
"doctype": "Desk Page", "doctype": "Desk Page",
"extends_another_page": 0, "extends_another_page": 0,
"icon": "",
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Accounting", "label": "Accounting",
"modified": "2020-04-29 12:17:34.844397", "modified": "2020-05-18 17:27:26.882340",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting", "name": "Accounting",
"onboarding": "Accounts",
"owner": "Administrator", "owner": "Administrator",
"pin_to_bottom": 0, "pin_to_bottom": 0,
"pin_to_top": 0, "pin_to_top": 0,
"shortcuts": [ "shortcuts": [
{ {
"label": "Account", "label": "Chart Of Accounts",
"link_to": "Account", "link_to": "Account",
"type": "DocType" "type": "DocType"
}, },
{
"label": "Sales Invoice",
"link_to": "Sales Invoice",
"type": "DocType"
},
{
"label": "Purchase Invoice",
"link_to": "Purchase Invoice",
"type": "DocType"
},
{
"label": "Accounts Dashboard",
"link_to": "Accounts Dashboard",
"type": "Dashboard"
},
{ {
"label": "Journal Entry", "label": "Journal Entry",
"link_to": "Journal Entry", "link_to": "Journal Entry",
@ -136,11 +146,6 @@
"link_to": "General Ledger", "link_to": "General Ledger",
"type": "Report" "type": "Report"
}, },
{
"label": "Profit and Loss Statement",
"link_to": "Profit and Loss Statement",
"type": "Report"
},
{ {
"label": "Trial Balance", "label": "Trial Balance",
"link_to": "Trial Balance", "link_to": "Trial Balance",

View File

@ -162,9 +162,9 @@ def toggle_disabling(doc):
def get_doctypes_with_dimensions(): def get_doctypes_with_dimensions():
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", 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", "Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", "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", "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", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"] "Subscription Plan"]

View File

@ -112,8 +112,8 @@ class GLEntry(Document):
from tabAccount where name=%s""", self.account, as_dict=1)[0] from tabAccount where name=%s""", self.account, as_dict=1)[0]
if ret.is_group==1: if ret.is_group==1:
frappe.throw(_("{0} {1}: Account {2} cannot be a Group") frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in
.format(self.voucher_type, self.voucher_no, self.account)) transactions''').format(self.voucher_type, self.voucher_no, self.account))
if ret.docstatus==2: if ret.docstatus==2:
frappe.throw(_("{0} {1}: Account {2} is inactive") frappe.throw(_("{0} {1}: Account {2} is inactive")

View File

@ -8,6 +8,7 @@ from frappe import _
from frappe.utils import flt, getdate, nowdate, add_days from frappe.utils import flt, getdate, nowdate, add_days
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class InvoiceDiscounting(AccountsController): class InvoiceDiscounting(AccountsController):
def validate(self): def validate(self):
@ -81,10 +82,15 @@ class InvoiceDiscounting(AccountsController):
def make_gl_entries(self): def make_gl_entries(self):
company_currency = frappe.get_cached_value('Company', self.company, "default_currency") company_currency = frappe.get_cached_value('Company', self.company, "default_currency")
gl_entries = [] 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: for d in self.invoices:
inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, invoice_fields, as_dict=1)
["debit_to", "party_account_currency", "conversion_rate", "cost_center"], as_dict=1)
if d.outstanding_amount: if d.outstanding_amount:
outstanding_in_company_currency = flt(d.outstanding_amount * inv.conversion_rate, outstanding_in_company_currency = flt(d.outstanding_amount * inv.conversion_rate,
@ -102,7 +108,7 @@ class InvoiceDiscounting(AccountsController):
"cost_center": inv.cost_center, "cost_center": inv.cost_center,
"against_voucher": d.sales_invoice, "against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice" "against_voucher_type": "Sales Invoice"
}, inv.party_account_currency)) }, inv.party_account_currency, item=inv))
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": self.accounts_receivable_credit, "account": self.accounts_receivable_credit,
@ -115,7 +121,7 @@ class InvoiceDiscounting(AccountsController):
"cost_center": inv.cost_center, "cost_center": inv.cost_center,
"against_voucher": d.sales_invoice, "against_voucher": d.sales_invoice,
"against_voucher_type": "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') make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')

View File

@ -86,7 +86,7 @@ class PaymentEntry(AccountsController):
self.update_payment_schedule(cancel=1) self.update_payment_schedule(cancel=1)
self.set_payment_req_status() self.set_payment_req_status()
self.set_status() self.set_status()
def set_payment_req_status(self): def set_payment_req_status(self):
from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status
update_payment_req_status(self, None) update_payment_req_status(self, None)
@ -280,7 +280,7 @@ class PaymentEntry(AccountsController):
outstanding_amount, is_return = frappe.get_cached_value(d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"]) 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: if outstanding_amount <= 0 and not is_return:
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d) no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
for k, v in no_oustanding_refs.items(): 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>\ 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.") If this is undesirable please cancel the corresponding Payment Entry.")
@ -451,8 +451,6 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction")) frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
def set_remarks(self): def set_remarks(self):
if self.remarks: return
if self.payment_type=="Internal Transfer": if self.payment_type=="Internal Transfer":
remarks = [_("Amount {0} {1} transferred from {2} to {3}") remarks = [_("Amount {0} {1} transferred from {2} to {3}")
.format(self.paid_from_account_currency, self.paid_amount, self.paid_from, self.paid_to)] .format(self.paid_from_account_currency, self.paid_amount, self.paid_from, self.paid_to)]
@ -506,7 +504,7 @@ class PaymentEntry(AccountsController):
"against": against_account, "against": against_account,
"account_currency": self.party_account_currency, "account_currency": self.party_account_currency,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit" dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit"
@ -550,7 +548,7 @@ class PaymentEntry(AccountsController):
"credit_in_account_currency": self.paid_amount, "credit_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount, "credit": self.base_paid_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
) )
if self.payment_type in ("Receive", "Internal Transfer"): if self.payment_type in ("Receive", "Internal Transfer"):
gl_entries.append( gl_entries.append(
@ -561,7 +559,7 @@ class PaymentEntry(AccountsController):
"debit_in_account_currency": self.received_amount, "debit_in_account_currency": self.received_amount,
"debit": self.base_received_amount, "debit": self.base_received_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
) )
def add_deductions_gl_entries(self, gl_entries): def add_deductions_gl_entries(self, gl_entries):

View File

@ -99,7 +99,7 @@ class PricingRule(Document):
self.same_item = 1 self.same_item = 1
def validate_max_discount(self): 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: for d in self.items:
max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount") max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount")
if max_discount and flt(self.discount_percentage) > flt(max_discount): if max_discount and flt(self.discount_percentage) > flt(max_discount):

View File

@ -4,13 +4,19 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, copy, json
from frappe import throw, _ import copy
import json
from six import string_types 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.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor 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 class MultiplePricingRuleConflict(frappe.ValidationError): pass
@ -502,18 +508,16 @@ def get_pricing_rule_items(pr_doc):
return list(set(apply_on_data)) return list(set(apply_on_data))
def validate_coupon_code(coupon_name): 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:
if coupon.valid_from > getdate(today()) : if coupon.valid_from > getdate(today()):
frappe.throw(_("Sorry,coupon code validity has not started")) frappe.throw(_("Sorry, this coupon code's validity has not started"))
elif coupon.valid_upto: elif coupon.valid_upto:
if coupon.valid_upto < getdate(today()) : if coupon.valid_upto < getdate(today()):
frappe.throw(_("Sorry,coupon code validity has expired")) frappe.throw(_("Sorry, this coupon code's validity has expired"))
elif coupon.used>=coupon.maximum_use: elif coupon.used >= coupon.maximum_use:
frappe.throw(_("Sorry,coupon code are exhausted")) frappe.throw(_("Sorry, this coupon code is no longer valid"))
else:
return
def update_coupon_code_count(coupon_name,transaction_type): def update_coupon_code_count(coupon_name,transaction_type):
coupon=frappe.get_doc("Coupon Code",coupon_name) coupon=frappe.get_doc("Coupon Code",coupon_name)

View File

@ -460,7 +460,7 @@ class PurchaseInvoice(BuyingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency) }, self.party_account_currency, item=self)
) )
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
@ -841,7 +841,7 @@ class PurchaseInvoice(BuyingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency) }, self.party_account_currency, item=self)
) )
gl_entries.append( gl_entries.append(
@ -852,7 +852,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_paid_amount \ "credit_in_account_currency": self.base_paid_amount \
if bank_account_currency==self.company_currency else self.paid_amount, if bank_account_currency==self.company_currency else self.paid_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}, bank_account_currency) }, bank_account_currency, item=self)
) )
def make_write_off_gl_entry(self, gl_entries): def make_write_off_gl_entry(self, gl_entries):
@ -873,7 +873,7 @@ class PurchaseInvoice(BuyingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency) }, self.party_account_currency, item=self)
) )
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
@ -883,7 +883,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_write_off_amount \ "credit_in_account_currency": self.base_write_off_amount \
if write_off_account_currency==self.company_currency else self.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 "cost_center": self.cost_center or self.write_off_cost_center
}) }, item=self)
) )
def make_gle_for_rounding_adjustment(self, gl_entries): def make_gle_for_rounding_adjustment(self, gl_entries):
@ -902,8 +902,7 @@ class PurchaseInvoice(BuyingController):
"debit_in_account_currency": self.rounding_adjustment, "debit_in_account_currency": self.rounding_adjustment,
"debit": self.base_rounding_adjustment, "debit": self.base_rounding_adjustment,
"cost_center": self.cost_center or round_off_cost_center, "cost_center": self.cost_center or round_off_cost_center,
} }, item=self))
))
def on_cancel(self): def on_cancel(self):
super(PurchaseInvoice, self).on_cancel() super(PurchaseInvoice, self).on_cancel()
@ -1021,6 +1020,40 @@ class PurchaseInvoice(BuyingController):
# calculate totals again after applying TDS # calculate totals again after applying TDS
self.calculate_taxes_and_totals() 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): def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context from erpnext.controllers.website_list_for_contact import get_list_context

View File

@ -86,6 +86,8 @@ class TestPurchaseInvoice(unittest.TestCase):
pe.submit() pe.submit()
pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name) 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) self.assertRaises(frappe.LinkExistsError, pi_doc.cancel)
unlink_payment_on_cancel_of_invoice() unlink_payment_on_cancel_of_invoice()
@ -203,7 +205,9 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.insert() pi.insert()
pi.submit() pi.submit()
pi.load_from_db()
self.assertTrue(pi.status, "Unpaid")
self.check_gle_for_pi(pi.name) self.check_gle_for_pi(pi.name)
def check_gle_for_pi(self, pi): def check_gle_for_pi(self, pi):
@ -234,6 +238,9 @@ class TestPurchaseInvoice(unittest.TestCase):
pi = frappe.copy_doc(test_records[0]) pi = frappe.copy_doc(test_records[0])
pi.insert() pi.insert()
pi.load_from_db()
self.assertTrue(pi.status, "Draft")
pi.naming_series = 'TEST-' pi.naming_series = 'TEST-'
self.assertRaises(frappe.CannotChangeConstantError, pi.save) self.assertRaises(frappe.CannotChangeConstantError, pi.save)
@ -248,6 +255,8 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.get("taxes").pop(1) pi.get("taxes").pop(1)
pi.insert() pi.insert()
pi.submit() pi.submit()
pi.load_from_db()
self.assertTrue(pi.status, "Unpaid")
gl_entries = frappe.db.sql("""select account, debit, credit gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
@ -599,6 +608,11 @@ class TestPurchaseInvoice(unittest.TestCase):
# return entry # return entry
pi1 = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, rate=50, update_stock=1) 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() actual_qty_2 = get_qty_after_transaction()
self.assertEqual(actual_qty_1 - 2, actual_qty_2) self.assertEqual(actual_qty_1 - 2, actual_qty_2)
@ -771,6 +785,8 @@ class TestPurchaseInvoice(unittest.TestCase):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import get_outstanding_amount 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 = 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, outstanding_amount = get_outstanding_amount(pi.doctype,
pi.name, "Creditors - _TC", pi.supplier, "Supplier") pi.name, "Creditors - _TC", pi.supplier, "Supplier")

View File

@ -924,7 +924,7 @@ var get_healthcare_services_to_invoice = function(frm) {
if(patient && patient!=selected_patient){ if(patient && patient!=selected_patient){
selected_patient = patient; selected_patient = patient;
var method = "erpnext.healthcare.utils.get_healthcare_services_to_invoice"; 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"]); var columns = (["service", "reference_name", "reference_type"]);
get_healthcare_items(frm, true, $results, $placeholder, method, args, columns); get_healthcare_items(frm, true, $results, $placeholder, method, args, columns);
} }
@ -1068,7 +1068,11 @@ var get_drugs_to_invoice = function(frm) {
description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.', 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) { get_query: function(doc) {
return { return {
filters: { patient: dialog.get_value("patient"), docstatus: 1 } filters: {
patient: dialog.get_value("patient"),
company: frm.doc.company,
docstatus: 1
}
}; };
} }
}, },

View File

@ -791,7 +791,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency) }, self.party_account_currency, item=self)
) )
def make_tax_gl_entries(self, gl_entries): def make_tax_gl_entries(self, gl_entries):
@ -808,7 +808,7 @@ class SalesInvoice(SellingController):
tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else 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"))), flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))),
"cost_center": tax.cost_center "cost_center": tax.cost_center
}, account_currency) }, account_currency, item=tax)
) )
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
@ -828,7 +828,7 @@ class SalesInvoice(SellingController):
for gle in fixed_asset_gl_entries: for gle in fixed_asset_gl_entries:
gle["against"] = self.customer 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.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None) asset.set_status("Sold" if self.docstatus==1 else None)
@ -866,7 +866,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
) )
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
@ -875,7 +875,7 @@ class SalesInvoice(SellingController):
"against": self.customer, "against": self.customer,
"debit": self.loyalty_amount, "debit": self.loyalty_amount,
"remark": "Loyalty Points redeemed by the customer" "remark": "Loyalty Points redeemed by the customer"
}) }, item=self)
) )
def make_pos_gl_entries(self, gl_entries): def make_pos_gl_entries(self, gl_entries):
@ -896,7 +896,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "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) payment_mode_account_currency = get_account_currency(payment_mode.account)
@ -909,7 +909,7 @@ class SalesInvoice(SellingController):
if payment_mode_account_currency==self.company_currency \ if payment_mode_account_currency==self.company_currency \
else payment_mode.amount, else payment_mode.amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}, payment_mode_account_currency) }, payment_mode_account_currency, item=self)
) )
def make_gle_for_change_amount(self, gl_entries): def make_gle_for_change_amount(self, gl_entries):
@ -927,7 +927,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency) }, self.party_account_currency, item=self)
) )
gl_entries.append( gl_entries.append(
@ -936,7 +936,7 @@ class SalesInvoice(SellingController):
"against": self.customer, "against": self.customer,
"credit": self.base_change_amount, "credit": self.base_change_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
) )
else: else:
frappe.throw(_("Select change amount account"), title="Mandatory Field") frappe.throw(_("Select change amount account"), title="Mandatory Field")
@ -960,7 +960,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency) }, self.party_account_currency, item=self)
) )
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
@ -971,7 +971,7 @@ class SalesInvoice(SellingController):
self.precision("base_write_off_amount")) if write_off_account_currency==self.company_currency 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"))), 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 "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): def make_gle_for_rounding_adjustment(self, gl_entries):
@ -988,8 +988,7 @@ class SalesInvoice(SellingController):
"credit": flt(self.base_rounding_adjustment, "credit": flt(self.base_rounding_adjustment,
self.precision("base_rounding_adjustment")), self.precision("base_rounding_adjustment")),
"cost_center": self.cost_center or round_off_cost_center, "cost_center": self.cost_center or round_off_cost_center,
} }, item=self))
))
def update_billing_status_in_dn(self, update_modified=True): def update_billing_status_in_dn(self, update_modified=True):
updated_delivery_notes = [] updated_delivery_notes = []

View File

@ -58,7 +58,7 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
"rate": tax_rate_detail.tax_withholding_rate, "rate": tax_rate_detail.tax_withholding_rate,
"threshold": tax_rate_detail.single_threshold, "threshold": tax_rate_detail.single_threshold,
"cumulative_threshold": tax_rate_detail.cumulative_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): def get_tax_withholding_rates(tax_withholding, fiscal_year):

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -546,7 +546,7 @@ class ReceivablePayableReport(object):
self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4 = 30, 60, 90, 120 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]): 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 index = i
break break

View File

@ -56,14 +56,26 @@ def execute(filters=None):
row += totals row += totals
data.append(row) data.append(row)
return columns, data chart = get_chart_data(filters, columns, data)
return columns, data, None, chart
def get_columns(filters): def get_columns(filters):
columns = [ columns = [
_(filters.get("budget_against")) {
+ ":Link/%s:150" % (filters.get("budget_against")), 'label': _(filters.get("budget_against")),
_("Account") + ":Link/Account:150" '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 group_months = False if filters["period"] == "Monthly" else True
@ -79,7 +91,12 @@ def get_columns(filters):
_("Variance ") + " " + str(year[0]) _("Variance ") + " " + str(year[0])
] ]
for label in labels: for label in labels:
columns.append(label + ":Float:150") columns.append({
'label': label,
'fieldtype': 'Float',
'fieldname': frappe.scrub(label),
'width': 150
})
else: else:
for label in [ for label in [
_("Budget") + " (%s)" + " " + str(year[0]), _("Budget") + " (%s)" + " " + str(year[0]),
@ -95,14 +112,23 @@ def get_columns(filters):
else: else:
label = label % formatdate(from_date, format_string="MMM") 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": if filters["period"] != "Yearly":
return columns + [ for label in [_("Total Budget"), _("Total Actual"), _("Total Variance")]:
_("Total Budget") + ":Float:150", columns.append({
_("Total Actual") + ":Float:150", 'label': label,
_("Total Variance") + ":Float:150" 'fieldtype': 'Float',
] 'fieldname': frappe.scrub(label),
'width': 150
})
return columns
else: else:
return columns return columns
@ -173,7 +199,7 @@ def get_dimension_target_details(filters):
filters.budget_against, filters.budget_against,
filters.company, filters.company,
] ]
+ filters.get("budget_against_filter") + (filters.get("budget_against_filter") or [])
), as_dict=True) ), as_dict=True)
@ -305,3 +331,49 @@ def get_fiscal_years(filters):
}) })
return fiscal_year 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}
]
}
}

View File

@ -53,7 +53,7 @@ frappe.query_reports["General Ledger"] = {
"label": __("Voucher No"), "label": __("Voucher No"),
"fieldtype": "Data", "fieldtype": "Data",
on_change: function() { on_change: function() {
frappe.query_report.set_filter_value('group_by', ""); frappe.query_report.set_filter_value('group_by', "Group by Voucher (Consolidated)");
} }
}, },
{ {

View File

@ -46,7 +46,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"default": frappe.defaults.get_user_default("year_end_date"), "default": frappe.defaults.get_user_default("year_end_date"),
}, },
{ {
"fieldname":"cost_center", "fieldname": "cost_center",
"label": __("Cost Center"), "label": __("Cost Center"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Cost Center", "options": "Cost Center",
@ -61,7 +61,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
} }
}, },
{ {
"fieldname":"finance_book", "fieldname": "project",
"label": __("Project"),
"fieldtype": "Link",
"options": "Project"
},
{
"fieldname": "finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Finance Book", "options": "Finance Book",
@ -97,7 +103,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
} }
erpnext.dimension_filters.forEach((dimension) => { 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"], "fieldname": dimension["fieldname"],
"label": __(dimension["label"]), "label": __(dimension["label"]),
"fieldtype": "Link", "fieldtype": "Link",

View File

@ -69,6 +69,10 @@ def get_data(filters):
gl_entries_by_account = {} gl_entries_by_account = {}
opening_balances = get_opening_balances(filters) 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, 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)) 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 @@ def get_rootwise_opening_balances(filters, report_type):
additional_conditions += """ and cost_center in (select name from `tabCost Center` additional_conditions += """ and cost_center in (select name from `tabCost Center`
where lft >= %s and rgt <= %s)""" % (lft, rgt) where lft >= %s and rgt <= %s)""" % (lft, rgt)
if filters.project:
additional_conditions += " and project = %(project)s"
if filters.finance_book: if filters.finance_book:
fb_conditions = " AND finance_book = %(finance_book)s" fb_conditions = " AND finance_book = %(finance_book)s"
if filters.include_default_book_entries: if filters.include_default_book_entries:
@ -116,6 +123,7 @@ def get_rootwise_opening_balances(filters, report_type):
"from_date": filters.from_date, "from_date": filters.from_date,
"report_type": report_type, "report_type": report_type,
"year_start_date": filters.year_start_date, "year_start_date": filters.year_start_date,
"project": filters.project,
"finance_book": filters.finance_book, "finance_book": filters.finance_book,
"company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book') "company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book')
} }

View File

@ -125,7 +125,7 @@ class Asset(AccountsController):
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): 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")) frappe.throw(_("Available-for-use Date should be after purchase date"))
def validate_gross_and_purchase_amount(self): def validate_gross_and_purchase_amount(self):
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount: 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. {}\ frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\
@ -455,7 +455,7 @@ class Asset(AccountsController):
for d in self.get('finance_books'): for d in self.get('finance_books'):
if d.finance_book == self.default_finance_book: if d.finance_book == self.default_finance_book:
return cint(d.idx) - 1 return cint(d.idx) - 1
def validate_make_gl_entry(self): def validate_make_gl_entry(self):
purchase_document = self.get_purchase_document() purchase_document = self.get_purchase_document()
asset_bought_with_invoice = purchase_document == self.purchase_invoice asset_bought_with_invoice = purchase_document == self.purchase_invoice
@ -487,14 +487,14 @@ class Asset(AccountsController):
purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
return purchase_document return purchase_document
def get_asset_accounts(self): def get_asset_accounts(self):
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name, fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
asset_category = self.asset_category, company = self.company) asset_category = self.asset_category, company = self.company)
cwip_account = get_asset_account("capital_work_in_progress_account", cwip_account = get_asset_account("capital_work_in_progress_account",
self.name, self.asset_category, self.company) self.name, self.asset_category, self.company)
return fixed_asset_account, cwip_account return fixed_asset_account, cwip_account
def make_gl_entries(self): def make_gl_entries(self):
@ -513,7 +513,7 @@ class Asset(AccountsController):
"credit": self.purchase_receipt_amount, "credit": self.purchase_receipt_amount,
"credit_in_account_currency": self.purchase_receipt_amount, "credit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
})) }, item=self))
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": fixed_asset_account, "account": fixed_asset_account,
@ -523,7 +523,7 @@ class Asset(AccountsController):
"debit": self.purchase_receipt_amount, "debit": self.purchase_receipt_amount,
"debit_in_account_currency": self.purchase_receipt_amount, "debit_in_account_currency": self.purchase_receipt_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
})) }, item=self))
if gl_entries: if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries

View File

@ -18,6 +18,10 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
refresh: function() { refresh: function() {
var me = this; var me = this;
this._super(); 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) { if (this.frm.doc.docstatus === 1) {
cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order, cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order,
__('Create')); __('Create'));

View File

@ -13,9 +13,10 @@
"supplier", "supplier",
"supplier_name", "supplier_name",
"column_break1", "column_break1",
"transaction_date",
"amended_from",
"company", "company",
"transaction_date",
"valid_till",
"amended_from",
"address_section", "address_section",
"supplier_address", "supplier_address",
"contact_person", "contact_person",
@ -791,13 +792,18 @@
"options": "Opportunity", "options": "Opportunity",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "valid_till",
"fieldtype": "Date",
"label": "Valid Till"
} }
], ],
"icon": "fa fa-shopping-cart", "icon": "fa fa-shopping-cart",
"idx": 29, "idx": 29,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2019-12-30 19:17:28.208693", "modified": "2020-04-15 11:44:52.958022",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier Quotation", "name": "Supplier Quotation",

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ 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 frappe.model.mapper import get_mapped_doc
from erpnext.controllers.buying_controller import BuyingController from erpnext.controllers.buying_controller import BuyingController
@ -28,6 +28,7 @@ class SupplierQuotation(BuyingController):
validate_for_items(self) validate_for_items(self)
self.validate_with_previous_doc() self.validate_with_previous_doc()
self.validate_uom_is_integer("uom", "qty") self.validate_uom_is_integer("uom", "qty")
self.validate_valid_till()
def on_submit(self): def on_submit(self):
frappe.db.set(self, "status", "Submitted") frappe.db.set(self, "status", "Submitted")
@ -52,6 +53,11 @@ class SupplierQuotation(BuyingController):
"is_child_table": True "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): def update_rfq_supplier_status(self, include_me):
rfq_list = set([]) rfq_list = set([])
for item in self.items: for item in self.items:
@ -158,3 +164,11 @@ def make_quotation(source_name, target_doc=None):
}, target_doc) }, target_doc)
return doclist 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()))

View File

@ -5,6 +5,8 @@ frappe.listview_settings['Supplier Quotation'] = {
return [__("Ordered"), "green", "status,=,Ordered"]; return [__("Ordered"), "green", "status,=,Ordered"];
} else if(doc.status==="Rejected") { } else if(doc.status==="Rejected") {
return [__("Lost"), "darkgrey", "status,=,Lost"]; return [__("Lost"), "darkgrey", "status,=,Lost"];
} else if(doc.status==="Expired") {
return [__("Expired"), "darkgrey", "status,=,Expired"];
} }
} }
}; };

View File

@ -5,20 +5,18 @@ frappe.query_reports["Quoted Item Comparison"] = {
filters: [ filters: [
{ {
fieldtype: "Link", fieldtype: "Link",
label: __("Supplier Quotation"), label: __("Company"),
options: "Supplier Quotation", options: "Company",
fieldname: "supplier_quotation", fieldname: "company",
default: "", default: frappe.defaults.get_user_default("Company"),
get_query: () => { "reqd": 1
return { filters: { "docstatus": ["<", 2] } }
}
}, },
{ {
reqd: 1, reqd: 1,
default: "", default: "",
options: "Item", options: "Item",
label: __("Item"), label: __("Item"),
fieldname: "item", fieldname: "item_code",
fieldtype: "Link", fieldtype: "Link",
get_query: () => { get_query: () => {
let quote = frappe.query_report.get_filter_value('supplier_quotation'); let quote = frappe.query_report.get_filter_value('supplier_quotation');
@ -37,8 +35,37 @@ frappe.query_reports["Quoted Item Comparison"] = {
} }
} }
} }
},
{
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) => { onload: (report) => {
// Create a button for setting the default supplier // Create a button for setting the default supplier
report.page.add_inner_button(__("Select Default Supplier"), () => { report.page.add_inner_button(__("Select Default Supplier"), () => {
@ -102,6 +129,4 @@ frappe.query_reports["Quoted Item Comparison"] = {
}); });
dialog.show(); dialog.show();
} }
} }

View File

@ -2,103 +2,180 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
from erpnext.setup.utils import get_exchange_rate
from frappe.utils import flt, cint
import frappe 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): def execute(filters=None):
qty_list = get_quantity_list(filters.item) if not filters:
data = get_quote_list(filters.item, qty_list) return [], []
columns = get_columns(qty_list)
return columns, data conditions = get_conditions(filters)
supplier_quotation_data = get_data(filters, conditions)
def get_quote_list(item, qty_list): columns = get_columns()
out = []
if not item: 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 [] return []
suppliers = [] supplier_quotation_data = frappe.db.sql("""SELECT
price_data = [] sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
company_currency = frappe.db.get_default("currency") sq.supplier
float_precision = cint(frappe.db.get_default("float_precision")) or 2 FROM
# Get the list of suppliers `tabSupplier Quotation Item` sqi,
for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item` `tabSupplier Quotation` sq
where item_code=%s and docstatus < 2""", item, as_dict=1): WHERE
for splr in frappe.db.sql("""select supplier from `tabSupplier Quotation` sqi.item_code = %(item_code)s
where name =%s and docstatus < 2""", root.parent, as_dict=1): AND sqi.parent = sq.name
ip = frappe._dict({ AND sqi.docstatus < 2
"supplier": splr.supplier, AND sq.company = %(company)s
"qty": root.qty, AND sq.status != 'Expired'
"parent": root.parent, {0}""".format(conditions), filters, as_dict=1)
"rate": root.rate
}) return supplier_quotation_data
price_data.append(ip)
suppliers.append(splr.supplier) 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")
#Add a row for each supplier
for root in set(suppliers):
supplier_currency = frappe.db.get_value("Supplier", root, "default_currency")
if supplier_currency: if supplier_currency:
exchange_rate = get_exchange_rate(supplier_currency, company_currency) exchange_rate = get_exchange_rate(supplier_currency, company_currency)
else: else:
exchange_rate = 1 exchange_rate = 1
row = frappe._dict({ row = {
"supplier_name": root "quotation": data.get("parent"),
}) "qty": data.get("qty"),
for col in qty_list: "price": flt(data.get("rate") * exchange_rate, float_precision),
# Get the quantity for this row "uom": data.get("uom"),
for item_price in price_data: "request_for_quotation": data.get("request_for_quotation"),
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)
for qt in qty_list: # map for report view of form {'supplier1':[{},{},...]}
col = frappe._dict({ supplier_wise_map[supplier].append(row)
"key": str(qt.qty),
"label": "Qty: " + str(int(qt.qty))
})
out.append(col)
return out # map for chart preparation of the form {'supplier1': {'qty': 'price'}}
if not supplier in supplier_qty_price_map:
def get_columns(qty_list): 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 = [{ columns = [{
"fieldname": "supplier_name", "fieldname": "supplier_name",
"label": "Supplier", "label": _("Supplier"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Supplier", "options": "Supplier",
"width": 200 "width": 200
}] },
{
for qty in qty_list: "fieldname": "quotation",
columns.append({ "label": _("Supplier Quotation"),
"fieldname": qty.key, "fieldtype": "Link",
"label": qty.label, "options": "Supplier Quotation",
"fieldtype": "Currency", "width": 200
"options": "currency", },
"width": 80 {
}) "fieldname": "qty",
columns.append({ "label": _("Quantity"),
"fieldname": qty.key + "QUOTE", "fieldtype": "Float",
"label": "Quotation", "width": 80
"fieldtype": "Link", },
"options": "Supplier Quotation", {
"width": 90 "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 return columns

View File

@ -8,11 +8,14 @@ from frappe.desk.reportview import get_match_cond, get_filters_cond
from frappe.utils import nowdate, getdate from frappe.utils import nowdate, getdate
from collections import defaultdict from collections import defaultdict
from erpnext.stock.get_item_details import _get_item_tax_template from erpnext.stock.get_item_details import _get_item_tax_template
from frappe.utils import unique
# searches for active employees # searches for active employees
def employee_query(doctype, txt, searchfield, start, page_len, filters): def employee_query(doctype, txt, searchfield, start, page_len, filters):
conditions = [] 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' where status = 'Active'
and docstatus < 2 and docstatus < 2
and ({key} like %(txt)s and ({key} like %(txt)s
@ -24,6 +27,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
idx desc, idx desc,
name, employee_name name, employee_name
limit %(start)s, %(page_len)s""".format(**{ limit %(start)s, %(page_len)s""".format(**{
'fields': ", ".join(fields),
'key': searchfield, 'key': searchfield,
'fcond': get_filters_cond(doctype, filters, conditions), 'fcond': get_filters_cond(doctype, filters, conditions),
'mcond': get_match_cond(doctype) 'mcond': get_match_cond(doctype)
@ -34,9 +38,12 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
'page_len': page_len '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): 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 where docstatus < 2
and ifnull(status, '') != 'Converted' and ifnull(status, '') != 'Converted'
and ({key} like %(txt)s and ({key} like %(txt)s
@ -50,6 +57,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
idx desc, idx desc,
name, lead_name name, lead_name
limit %(start)s, %(page_len)s""".format(**{ limit %(start)s, %(page_len)s""".format(**{
'fields': ", ".join(fields),
'key': searchfield, 'key': searchfield,
'mcond':get_match_cond(doctype) 'mcond':get_match_cond(doctype)
}), { }), {
@ -59,6 +67,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
'page_len': page_len 'page_len': page_len
}) })
# searches for customer # searches for customer
def customer_query(doctype, txt, searchfield, start, page_len, filters): def customer_query(doctype, txt, searchfield, start, page_len, filters):
conditions = [] conditions = []
@ -69,13 +78,9 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
else: else:
fields = ["name", "customer_name", "customer_group", "territory"] fields = ["name", "customer_name", "customer_group", "territory"]
meta = frappe.get_meta("Customer") fields = get_fields("Customer", fields)
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 = ", ".join(fields) searchfields = frappe.get_meta("Customer").get_search_fields()
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
return frappe.db.sql("""select {fields} from `tabCustomer` return frappe.db.sql("""select {fields} from `tabCustomer`
@ -88,7 +93,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
idx desc, idx desc,
name, customer_name name, customer_name
limit %(start)s, %(page_len)s""".format(**{ limit %(start)s, %(page_len)s""".format(**{
"fields": fields, "fields": ", ".join(fields),
"scond": searchfields, "scond": searchfields,
"mcond": get_match_cond(doctype), "mcond": get_match_cond(doctype),
"fcond": get_filters_cond(doctype, filters, conditions).replace('%', '%%'), "fcond": get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
@ -99,6 +104,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
'page_len': page_len 'page_len': page_len
}) })
# searches for supplier # searches for supplier
def supplier_query(doctype, txt, searchfield, start, page_len, filters): def supplier_query(doctype, txt, searchfield, start, page_len, filters):
supp_master_name = frappe.defaults.get_user_default("supp_master_name") supp_master_name = frappe.defaults.get_user_default("supp_master_name")
@ -106,7 +112,8 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
fields = ["name", "supplier_group"] fields = ["name", "supplier_group"]
else: else:
fields = ["name", "supplier_name", "supplier_group"] fields = ["name", "supplier_name", "supplier_group"]
fields = ", ".join(fields)
fields = get_fields("Supplier", fields)
return frappe.db.sql("""select {field} from `tabSupplier` return frappe.db.sql("""select {field} from `tabSupplier`
where docstatus < 2 where docstatus < 2
@ -119,7 +126,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
idx desc, idx desc,
name, supplier_name name, supplier_name
limit %(start)s, %(page_len)s """.format(**{ limit %(start)s, %(page_len)s """.format(**{
'field': fields, 'field': ', '.join(fields),
'key': searchfield, 'key': searchfield,
'mcond':get_match_cond(doctype) 'mcond':get_match_cond(doctype)
}), { }), {
@ -129,6 +136,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
'page_len': page_len 'page_len': page_len
}) })
def tax_account_query(doctype, txt, searchfield, start, page_len, filters): def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
company_currency = erpnext.get_company_currency(filters.get('company')) company_currency = erpnext.get_company_currency(filters.get('company'))
@ -153,6 +161,7 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
return tax_accounts return tax_accounts
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
conditions = [] conditions = []
@ -221,10 +230,12 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
"page_len": page_len "page_len": page_len
}, as_dict=as_dict) }, as_dict=as_dict)
def bom(doctype, txt, searchfield, start, page_len, filters): def bom(doctype, txt, searchfield, start, page_len, filters):
conditions = [] conditions = []
fields = get_fields("BOM", ["name", "item"])
return frappe.db.sql("""select tabBOM.name, tabBOM.item return frappe.db.sql("""select {fields}
from tabBOM from tabBOM
where tabBOM.docstatus=1 where tabBOM.docstatus=1
and tabBOM.is_active=1 and tabBOM.is_active=1
@ -234,6 +245,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
idx desc, name idx desc, name
limit %(start)s, %(page_len)s """.format( limit %(start)s, %(page_len)s """.format(
fields=", ".join(fields),
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
mcond=get_match_cond(doctype).replace('%', '%%'), mcond=get_match_cond(doctype).replace('%', '%%'),
key=searchfield), key=searchfield),
@ -244,13 +256,16 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
'page_len': page_len or 20 'page_len': page_len or 20
}) })
def get_project_name(doctype, txt, searchfield, start, page_len, filters): def get_project_name(doctype, txt, searchfield, start, page_len, filters):
cond = '' cond = ''
if filters.get('customer'): if filters.get('customer'):
cond = """(`tabProject`.customer = %s or cond = """(`tabProject`.customer = %s or
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer"))) 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") where `tabProject`.status not in ("Completed", "Cancelled")
and {cond} `tabProject`.name like %(txt)s {match_cond} and {cond} `tabProject`.name like %(txt)s {match_cond}
order by order by
@ -258,6 +273,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
idx desc, idx desc,
`tabProject`.name asc `tabProject`.name asc
limit {start}, {page_len}""".format( limit {start}, {page_len}""".format(
fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]),
cond=cond, cond=cond,
match_cond=get_match_cond(doctype), match_cond=get_match_cond(doctype),
start=start, start=start,
@ -268,8 +284,10 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict): 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(""" return frappe.db.sql("""
select `tabDelivery Note`.name, `tabDelivery Note`.customer, `tabDelivery Note`.posting_date select %(fields)s
from `tabDelivery Note` from `tabDelivery Note`
where `tabDelivery Note`.`%(key)s` like %(txt)s and where `tabDelivery Note`.`%(key)s` like %(txt)s and
`tabDelivery Note`.docstatus = 1 `tabDelivery Note`.docstatus = 1
@ -284,6 +302,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
) )
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(start)s, %(page_len)s %(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, "key": searchfield,
"fcond": get_filters_cond(doctype, filters, []), "fcond": get_filters_cond(doctype, filters, []),
"mcond": get_match_cond(doctype), "mcond": get_match_cond(doctype),
@ -349,6 +368,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
order by expiry_date, name desc order by expiry_date, name desc
limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args) 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): def get_account_list(doctype, txt, searchfield, start, page_len, filters):
filter_list = [] filter_list = []
@ -371,6 +391,7 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
fields = ["name", "parent_account"], fields = ["name", "parent_account"],
limit_start=start, limit_page_length=page_len, as_list=True) limit_start=start, limit_page_length=page_len, as_list=True)
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): 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 return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date
from `tabBlanket Order` bo, `tabBlanket Order Item` boi from `tabBlanket Order` bo, `tabBlanket Order Item` boi
@ -385,6 +406,7 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
company = frappe.db.escape(filters.get("company")) company = frappe.db.escape(filters.get("company"))
)) ))
@frappe.whitelist() @frappe.whitelist()
def get_income_account(doctype, txt, searchfield, start, page_len, filters): def get_income_account(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond from erpnext.controllers.queries import get_match_cond
@ -490,6 +512,7 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(query, filters) return frappe.db.sql(query, filters)
@frappe.whitelist() @frappe.whitelist()
def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters): def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters):
item_filters = [ item_filters = [
@ -507,6 +530,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters)
) )
return item_manufacturers return item_manufacturers
@frappe.whitelist() @frappe.whitelist()
def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
query = """ query = """
@ -520,6 +544,7 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(query, filters) return frappe.db.sql(query, filters)
@frappe.whitelist() @frappe.whitelist()
def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
query = """ query = """
@ -533,6 +558,7 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(query, filters) return frappe.db.sql(query, filters)
@frappe.whitelist() @frappe.whitelist()
def get_tax_template(doctype, txt, searchfield, start, page_len, filters): def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
@ -556,3 +582,13 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
taxes = _get_item_tax_template(args, taxes, for_validate=True) taxes = _get_item_tax_template(args, taxes, for_validate=True)
return [(d,) for d in set(taxes)] 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)

View File

@ -69,17 +69,6 @@ status_map = {
["Cancelled", "eval:self.docstatus==2"], ["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"], ["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": [ "Material Request": [
["Draft", None], ["Draft", None],
["Stopped", "eval:self.status == 'Stopped'"], ["Stopped", "eval:self.status == 'Stopped'"],

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_events_in_timeline": 1, "allow_events_in_timeline": 1,
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
@ -447,7 +448,7 @@
"idx": 5, "idx": 5,
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"modified": "2020-04-08 22:26:11.687110", "modified": "2020-05-11 20:27:45.868960",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Lead", "name": "Lead",
@ -504,15 +505,6 @@
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Sales User" "role": "Sales User"
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Guest",
"share": 1
} }
], ],
"search_fields": "lead_name,lead_owner,status", "search_fields": "lead_name,lead_owner,status",

View File

@ -15,7 +15,7 @@ class LinkedInSettings(Document):
params = urlencode({ params = urlencode({
"response_type":"code", "response_type":"code",
"client_id": self.consumer_key, "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" "scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social"
}) })
@ -30,7 +30,7 @@ class LinkedInSettings(Document):
"code": code, "code": code,
"client_id": self.consumer_key, "client_id": self.consumer_key,
"client_secret": self.get_password(fieldname="consumer_secret"), "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 = { headers = {
"Content-Type": "application/x-www-form-urlencoded" "Content-Type": "application/x-www-form-urlencoded"

View File

@ -11,8 +11,8 @@
"consumer_key", "consumer_key",
"column_break_5", "column_break_5",
"consumer_secret", "consumer_secret",
"oauth_token", "access_token",
"oauth_secret", "access_token_secret",
"session_status" "session_status"
], ],
"fields": [ "fields": [
@ -41,20 +41,6 @@
"label": "API Secret Key", "label": "API Secret Key",
"reqd": 1 "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", "fieldname": "column_break_5",
"fieldtype": "Column Break" "fieldtype": "Column Break"
@ -72,12 +58,26 @@
"label": "Session Status", "label": "Session Status",
"options": "Expired\nActive", "options": "Expired\nActive",
"read_only": 1 "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", "image_field": "profile_pic",
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2020-04-21 22:06:43.726798", "modified": "2020-05-13 17:50:47.934776",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Twitter Settings", "name": "Twitter Settings",

View File

@ -31,13 +31,13 @@ class TwitterSettings(Document):
try: try:
auth.get_access_token(oauth_verifier) auth.get_access_token(oauth_verifier)
api = self.get_api() api = self.get_api(auth.access_token, auth.access_token_secret)
user = api.me() user = api.me()
profile_pic = (user._json["profile_image_url"]).replace("_normal","") profile_pic = (user._json["profile_image_url"]).replace("_normal","")
frappe.db.set_value(self.doctype, self.name, { frappe.db.set_value(self.doctype, self.name, {
"oauth_token" : auth.access_token, "access_token" : auth.access_token,
"oauth_secret" : auth.access_token_secret, "access_token_secret" : auth.access_token_secret,
"account_name" : user._json["screen_name"], "account_name" : user._json["screen_name"],
"profile_pic" : profile_pic, "profile_pic" : profile_pic,
"session_status" : "Active" "session_status" : "Active"
@ -49,11 +49,11 @@ class TwitterSettings(Document):
frappe.msgprint(_("Error! Failed to get access token.")) frappe.msgprint(_("Error! Failed to get access token."))
frappe.throw(_('Invalid Consumer Key or Consumer Secret Key')) 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 # authentication of consumer key and secret
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret")) auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
# authentication of access token and 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) return tweepy.API(auth)
@ -67,13 +67,13 @@ class TwitterSettings(Document):
def upload_image(self, media): def upload_image(self, media):
media = get_file_path(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) media = api.media_upload(media)
return media.media_id return media.media_id
def send_tweet(self, text, media_id=None): def send_tweet(self, text, media_id=None):
api = self.get_api() api = self.get_api(self.access_token, self.access_token_secret)
try: try:
if media_id: if media_id:
response = api.update_status(status = text, media_ids = [media_id]) response = api.update_status(status = text, media_ids = [media_id])

View File

@ -98,14 +98,16 @@ class Fees(AccountsController):
"debit_in_account_currency": self.grand_total, "debit_in_account_currency": self.grand_total,
"against_voucher": self.name, "against_voucher": self.name,
"against_voucher_type": self.doctype "against_voucher_type": self.doctype
}) }, item=self)
fee_gl_entry = self.get_gl_dict({ fee_gl_entry = self.get_gl_dict({
"account": self.income_account, "account": self.income_account,
"against": self.student, "against": self.student,
"credit": self.grand_total, "credit": self.grand_total,
"credit_in_account_currency": self.grand_total, "credit_in_account_currency": self.grand_total,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries([student_gl_entries, fee_gl_entry], cancel=(self.docstatus == 2), make_gl_entries([student_gl_entries, fee_gl_entry], cancel=(self.docstatus == 2),
update_outstanding="Yes", merge_entries=False) update_outstanding="Yes", merge_entries=False)

View File

@ -209,7 +209,7 @@ def new_bank_transaction(transaction):
result.append(new_transaction.name) result.append(new_transaction.name)
except Exception: except Exception:
frappe.throw(frappe.get_traceback()) frappe.throw(title=_('Bank transaction creation error'))
return result return result

View File

@ -43,7 +43,8 @@ frappe.ui.form.on('Clinical Procedure', {
return { return {
filters: { filters: {
'is_group': false, 'is_group': false,
'allow_appointments': true 'allow_appointments': true,
'company': frm.doc.company
} }
}; };
}); });
@ -158,11 +159,13 @@ frappe.ui.form.on('Clinical Procedure', {
age = __('{0} as on {1}', [age, data.message.age_as_on]); 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_age', age);
frm.set_value('patient_sex', data.message.sex); frm.set_value('patient_sex', data.message.sex);
} }
}); });
} else { } else {
frm.set_value('patient_name', '');
frm.set_value('patient_age', ''); frm.set_value('patient_age', '');
frm.set_value('patient_sex', ''); frm.set_value('patient_sex', '');
} }
@ -177,15 +180,35 @@ frappe.ui.form.on('Clinical Procedure', {
name: frm.doc.appointment name: frm.doc.appointment
}, },
callback: function(data) { callback: function(data) {
frm.set_value('patient', data.message.patient); let values = {
frm.set_value('procedure_template', data.message.procedure_template); 'patient':data.message.patient,
frm.set_value('medical_department', data.message.department); 'procedure_template': data.message.procedure_template,
frm.set_value('start_date', data.message.appointment_date); 'medical_department': data.message.department,
frm.set_value('start_time', data.message.appointment_time); 'practitioner': data.message.practitioner,
frm.set_value('notes', data.message.notes); 'start_date': data.message.appointment_date,
frm.set_value('service_unit', data.message.service_unit); '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 @@ frappe.ui.form.on('Clinical Procedure', {
name: frm.doc.practitioner name: frm.doc.practitioner
}, },
callback: function (data) { 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 @@ frappe.ui.form.on('Clinical Procedure', {
}); });
cur_frm.set_query('procedure_template', function(doc) {
return {
filters: {
'medical_department': doc.medical_department
}
};
});
frappe.ui.form.on('Clinical Procedure Item', { frappe.ui.form.on('Clinical Procedure Item', {
qty: function(frm, cdt, cdn) { qty: function(frm, cdt, cdn) {
let d = locals[cdt][cdn]; let d = locals[cdt][cdn];

View File

@ -7,28 +7,32 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"inpatient_record",
"naming_series", "naming_series",
"procedure_template", "title",
"appointment", "appointment",
"procedure_template",
"column_break_30",
"company",
"invoiced",
"section_break_6",
"patient", "patient",
"patient_name",
"patient_sex", "patient_sex",
"patient_age", "patient_age",
"prescription", "inpatient_record",
"medical_department", "notes",
"practitioner",
"column_break_7", "column_break_7",
"status", "status",
"practitioner",
"practitioner_name",
"medical_department",
"service_unit", "service_unit",
"warehouse",
"start_date", "start_date",
"start_time", "start_time",
"sample", "sample",
"invoiced",
"notes",
"company",
"consumables_section", "consumables_section",
"consume_stock", "consume_stock",
"warehouse",
"items", "items",
"section_break_24", "section_break_24",
"invoice_separately_as_consumables", "invoice_separately_as_consumables",
@ -36,6 +40,9 @@
"consumable_total_amount", "consumable_total_amount",
"column_break_27", "column_break_27",
"consumption_details", "consumption_details",
"sb_refs",
"column_break_34",
"prescription",
"amended_from" "amended_from"
], ],
"fields": [ "fields": [
@ -56,15 +63,15 @@
{ {
"fieldname": "appointment", "fieldname": "appointment",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_standard_filter": 1,
"label": "Appointment", "label": "Appointment",
"options": "Patient Appointment" "options": "Patient Appointment",
"set_only_once": 1
}, },
{ {
"fetch_from": "inpatient_record.patient",
"fieldname": "patient", "fieldname": "patient",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_standard_filter": 1,
"label": "Patient", "label": "Patient",
"options": "Patient", "options": "Patient",
"reqd": 1 "reqd": 1
@ -88,17 +95,20 @@
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "hidden": 1,
"label": "Procedure Prescription", "label": "Procedure Prescription",
"options": "Procedure Prescription" "options": "Procedure Prescription",
"read_only": 1
}, },
{ {
"fieldname": "medical_department", "fieldname": "medical_department",
"fieldtype": "Link", "fieldtype": "Link",
"in_standard_filter": 1,
"label": "Medical Department", "label": "Medical Department",
"options": "Medical Department" "options": "Medical Department"
}, },
{ {
"fieldname": "practitioner", "fieldname": "practitioner",
"fieldtype": "Link", "fieldtype": "Link",
"in_standard_filter": 1,
"label": "Healthcare Practitioner", "label": "Healthcare Practitioner",
"options": "Healthcare Practitioner" "options": "Healthcare Practitioner"
}, },
@ -208,6 +218,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval:!doc.__islocal",
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 1,
@ -226,6 +237,8 @@
"read_only": 1 "read_only": 1
}, },
{ {
"collapsible": 1,
"collapsible_depends_on": "consume_stock",
"fieldname": "consumables_section", "fieldname": "consumables_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Consumables" "label": "Consumables"
@ -237,11 +250,51 @@
{ {
"fieldname": "section_break_24", "fieldname": "section_break_24",
"fieldtype": "Section Break" "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, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-03-02 11:44:27.970651", "modified": "2020-04-27 21:36:23.796924",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Clinical Procedure", "name": "Clinical Procedure",
@ -257,11 +310,27 @@
"report": 1, "report": 1,
"role": "Nursing User", "role": "Nursing User",
"share": 1, "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 "write": 1
} }
], ],
"restrict_to_domain": "Healthcare", "restrict_to_domain": "Healthcare",
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "title",
"track_changes": 1 "track_changes": 1
} }

View File

@ -16,6 +16,7 @@ from frappe.model.mapper import get_mapped_doc
class ClinicalProcedure(Document): class ClinicalProcedure(Document):
def validate(self): def validate(self):
self.set_status() self.set_status()
self.set_title()
if self.consume_stock: if self.consume_stock:
self.set_actual_qty() self.set_actual_qty()
@ -37,7 +38,7 @@ class ClinicalProcedure(Document):
template = frappe.get_doc('Clinical Procedure Template', self.procedure_template) template = frappe.get_doc('Clinical Procedure Template', self.procedure_template)
if template.sample: if template.sample:
patient = frappe.get_doc('Patient', self.patient) 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) frappe.db.set_value('Clinical Procedure', self.name, 'sample', sample_collection.name)
self.reload() self.reload()
@ -50,6 +51,9 @@ class ClinicalProcedure(Document):
elif self.docstatus == 2: elif self.docstatus == 2:
self.status = 'Cancelled' 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): def complete_procedure(self):
if self.consume_stock and self.items: if self.consume_stock and self.items:
stock_entry = make_stock_entry(self) stock_entry = make_stock_entry(self)

View File

@ -1,6 +1,5 @@
{ {
"actions": [], "actions": [],
"allow_copy": 1,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
@ -51,17 +50,20 @@
"fieldname": "first_name", "fieldname": "first_name",
"fieldtype": "Data", "fieldtype": "Data",
"label": "First Name", "label": "First Name",
"no_copy": 1,
"reqd": 1 "reqd": 1
}, },
{ {
"fieldname": "middle_name", "fieldname": "middle_name",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Middle Name (Optional)" "label": "Middle Name (Optional)",
"no_copy": 1
}, },
{ {
"fieldname": "last_name", "fieldname": "last_name",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Last Name" "label": "Last Name",
"no_copy": 1
}, },
{ {
"fieldname": "image", "fieldname": "image",
@ -226,6 +228,7 @@
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Full Name", "label": "Full Name",
"no_copy": 1,
"read_only": 1, "read_only": 1,
"search_index": 1 "search_index": 1
}, },
@ -233,6 +236,7 @@
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Series", "label": "Series",
"no_copy": 1,
"options": "HLC-PRAC-.YYYY.-", "options": "HLC-PRAC-.YYYY.-",
"report_hide": 1, "report_hide": 1,
"set_only_once": 1 "set_only_once": 1

View File

@ -240,7 +240,7 @@
"label": "Patient Registration" "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", "depends_on": "send_registration_msg",
"fieldname": "registration_msg", "fieldname": "registration_msg",
"fieldtype": "Small Text", "fieldtype": "Small Text",
@ -254,7 +254,7 @@
"label": "Appointment Confirmation" "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", "depends_on": "send_appointment_confirmation",
"fieldname": "appointment_confirmation_msg", "fieldname": "appointment_confirmation_msg",
"fieldtype": "Small Text", "fieldtype": "Small Text",
@ -276,7 +276,7 @@
"label": "Appointment Reminder" "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", "depends_on": "send_appointment_reminder",
"fieldname": "appointment_reminder_msg", "fieldname": "appointment_reminder_msg",
"fieldtype": "Small Text", "fieldtype": "Small Text",

View File

@ -8,7 +8,6 @@ import unittest
from frappe.utils import now_datetime, today from frappe.utils import now_datetime, today
from frappe.utils.make_random import get_random 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.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): class TestInpatientRecord(unittest.TestCase):
def test_admit_and_discharge(self): def test_admit_and_discharge(self):
@ -112,3 +111,13 @@ def get_service_unit_type():
service_unit_type.save(ignore_permissions = True) service_unit_type.save(ignore_permissions = True)
return service_unit_type.name return service_unit_type.name
return service_unit_type 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

View File

@ -137,13 +137,13 @@ var get_lab_test_prescribed = function(frm){
}); });
} }
else{ 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 show_lab_tests = function(frm, result){
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __("Lab Test Prescriptions"), title: __("Lab Tests"),
fields: [ fields: [
{ {
fieldtype: "HTML", fieldname: "lab_test" fieldtype: "HTML", fieldname: "lab_test"
@ -161,7 +161,7 @@ var show_lab_tests = function(frm, result){
<div class="col-xs-1">\ <div class="col-xs-1">\
<a data-name="%(name)s" data-lab-test="%(lab_test)s"\ <a data-name="%(name)s" data-lab-test="%(lab_test)s"\
data-encounter="%(encounter)s" data-practitioner="%(practitioner)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); </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() { row.find("a").click(function() {
frm.doc.template = $(this).attr("data-lab-test"); frm.doc.template = $(this).attr("data-lab-test");
@ -180,9 +180,10 @@ var show_lab_tests = function(frm, result){
return false; return false;
}); });
}); });
if(!result){ if(!result.length){
var msg = "There are no Lab Test prescribed for "+frm.doc.patient; var msg = __("No Lab Tests found for the Patient {0}", [frm.doc.patient_name.bold()]);
$(repl('<div class="col-xs-12" style="padding-top:20px;" >%(msg)s</div></div>', {msg: msg})).appendTo(html_field); html_field.empty();
$(repl('<div class="col-xs-12" style="padding-top:0px;" >%(msg)s</div>', {msg: msg})).appendTo(html_field);
} }
d.show(); d.show();
}; };

View File

@ -9,18 +9,18 @@
"document_type": "Document", "document_type": "Document",
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"inpatient_record",
"naming_series", "naming_series",
"invoiced",
"patient", "patient",
"patient_name", "patient_name",
"patient_age", "patient_age",
"patient_sex", "patient_sex",
"practitioner", "report_preference",
"email", "email",
"mobile", "mobile",
"company", "practitioner",
"c_b", "c_b",
"inpatient_record",
"company",
"department", "department",
"status", "status",
"submitted_date", "submitted_date",
@ -31,7 +31,7 @@
"employee_name", "employee_name",
"employee_designation", "employee_designation",
"user", "user",
"report_preference", "invoiced",
"sb_first", "sb_first",
"lab_test_name", "lab_test_name",
"column_break_26", "column_break_26",
@ -153,7 +153,7 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "in_standard_filter": 1,
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"print_hide": 1, "print_hide": 1,
@ -168,6 +168,7 @@
"fieldname": "department", "fieldname": "department",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_standard_filter": 1,
"label": "Department", "label": "Department",
"options": "Medical Department", "options": "Medical Department",
"search_index": 1 "search_index": 1
@ -427,7 +428,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-03-23 19:37:06.617764", "modified": "2020-04-04 19:16:29.131168",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Lab Test", "name": "Lab Test",

View File

@ -69,9 +69,9 @@ def create_multiple(doctype, docname):
lab_test_created = create_lab_test_from_encounter(docname) lab_test_created = create_lab_test_from_encounter(docname)
if lab_test_created: 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: else:
frappe.msgprint(_("No Lab Test created")) frappe.msgprint(_("No Lab Tests created"))
def create_lab_test_from_encounter(encounter_id): def create_lab_test_from_encounter(encounter_id):
lab_test_created = False lab_test_created = False
@ -87,7 +87,7 @@ def create_lab_test_from_encounter(encounter_id):
for lab_test_id in lab_test_ids: for lab_test_id in lab_test_ids:
template = get_lab_test_template(lab_test_id[1]) template = get_lab_test_template(lab_test_id[1])
if template: 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) lab_test.save(ignore_permissions = True)
frappe.db.set_value("Lab Prescription", lab_test_id[0], "lab_test_created", 1) frappe.db.set_value("Lab Prescription", lab_test_id[0], "lab_test_created", 1)
if not lab_test_created: if not lab_test_created:
@ -111,7 +111,7 @@ def create_lab_test_from_invoice(invoice_name):
if lab_test_created != 1: if lab_test_created != 1:
template = get_lab_test_template(item.item_code) template = get_lab_test_template(item.item_code)
if template: 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": if item.reference_dt == "Lab Prescription":
lab_test.prescription = item.reference_dn lab_test.prescription = item.reference_dn
lab_test.save(ignore_permissions = True) lab_test.save(ignore_permissions = True)
@ -121,7 +121,7 @@ def create_lab_test_from_invoice(invoice_name):
if not lab_tests_created: if not lab_tests_created:
lab_tests_created = lab_test.name lab_tests_created = lab_test.name
else: else:
lab_tests_created += ", "+lab_test.name lab_tests_created += ", " + lab_test.name
return lab_tests_created return lab_tests_created
def get_lab_test_template(item): def get_lab_test_template(item):
@ -141,7 +141,7 @@ def check_template_exists(item):
return template_exists return template_exists
return False 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 = frappe.new_doc("Lab Test")
lab_test.invoiced = invoiced lab_test.invoiced = invoiced
lab_test.practitioner = practitioner lab_test.practitioner = practitioner
@ -150,11 +150,12 @@ def create_lab_test_doc(invoiced, practitioner, patient, template):
lab_test.patient_sex = patient.sex lab_test.patient_sex = patient.sex
lab_test.email = patient.email lab_test.email = patient.email
lab_test.mobile = patient.mobile lab_test.mobile = patient.mobile
lab_test.report_preference = patient.report_preference
lab_test.department = template.department lab_test.department = template.department
lab_test.template = template.name lab_test.template = template.name
lab_test.lab_test_group = template.lab_test_group lab_test.lab_test_group = template.lab_test_group
lab_test.result_date = getdate() lab_test.result_date = getdate()
lab_test.report_preference = patient.report_preference lab_test.company = company
return lab_test return lab_test
def create_normals(template, lab_test): def create_normals(template, lab_test):
@ -190,7 +191,7 @@ def create_specials(template, lab_test):
special.require_result_value = 1 special.require_result_value = 1
special.template = template.name special.template = template.name
def create_sample_doc(template, patient, invoice): def create_sample_doc(template, patient, invoice, company = None):
if template.sample: if template.sample:
sample_exists = frappe.db.exists({ sample_exists = frappe.db.exists({
"doctype": "Sample Collection", "doctype": "Sample Collection",
@ -221,6 +222,8 @@ def create_sample_doc(template, patient, invoice):
sample_collection.sample = template.sample sample_collection.sample = template.sample
sample_collection.sample_uom = template.sample_uom sample_collection.sample_uom = template.sample_uom
sample_collection.sample_qty = template.sample_qty sample_collection.sample_qty = template.sample_qty
sample_collection.company = company
if(template.sample_details): if(template.sample_details):
sample_collection.sample_details = "Test :" + (template.get("lab_test_name") or template.get("template")) +"\n"+"Collection Detials:\n\t"+template.sample_details sample_collection.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) sample_collection.save(ignore_permissions=True)
@ -229,7 +232,7 @@ def create_sample_doc(template, patient, invoice):
def create_sample_collection(lab_test, template, patient, invoice): def create_sample_collection(lab_test, template, patient, invoice):
if(frappe.db.get_value("Healthcare Settings", None, "create_sample_collection_for_lab_test") == "1"): if(frappe.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): if(sample_collection):
lab_test.sample = sample_collection.name lab_test.sample = sample_collection.name
return lab_test return lab_test

View File

@ -10,6 +10,8 @@ frappe.ui.form.on('Patient', {
] ]
}; };
}); });
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') { if (frappe.defaults.get_default('patient_name_by') != 'Naming Series') {
frm.toggle_display('naming_series', false); frm.toggle_display('naming_series', false);
@ -40,6 +42,7 @@ frappe.ui.form.on('Patient', {
frm.add_custom_button(__('Patient Encounter'), function () { frm.add_custom_button(__('Patient Encounter'), function () {
create_encounter(frm); create_encounter(frm);
}, 'Create'); }, 'Create');
frm.toggle_enable(['customer'], 0); // ToDo, allow change only if no transactions booked or better, add merge option
} }
}, },
onload: function (frm) { onload: function (frm) {

View File

@ -24,13 +24,20 @@
"image", "image",
"column_break_14", "column_break_14",
"status", "status",
"inpatient_status",
"inpatient_record", "inpatient_record",
"customer", "inpatient_status",
"report_preference",
"mobile", "mobile",
"email", "email",
"phone", "phone",
"report_preference", "customer_details_section",
"customer",
"customer_group",
"territory",
"column_break_24",
"default_currency",
"default_price_list",
"language",
"personal_and_social_history", "personal_and_social_history",
"occupation", "occupation",
"column_break_25", "column_break_25",
@ -52,9 +59,7 @@
"surrounding_factors", "surrounding_factors",
"other_risk_factors", "other_risk_factors",
"more_info", "more_info",
"patient_details", "patient_details"
"ac_sb",
"default_currency"
], ],
"fields": [ "fields": [
{ {
@ -67,6 +72,7 @@
{ {
"fieldname": "inpatient_status", "fieldname": "inpatient_status",
"fieldtype": "Select", "fieldtype": "Select",
"in_preview": 1,
"label": "Inpatient Status", "label": "Inpatient Status",
"options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled", "options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled",
"read_only": 1 "read_only": 1
@ -101,6 +107,7 @@
{ {
"fieldname": "sex", "fieldname": "sex",
"fieldtype": "Link", "fieldtype": "Link",
"in_preview": 1,
"label": "Gender", "label": "Gender",
"options": "Gender", "options": "Gender",
"reqd": 1 "reqd": 1
@ -109,6 +116,7 @@
"bold": 1, "bold": 1,
"fieldname": "blood_group", "fieldname": "blood_group",
"fieldtype": "Select", "fieldtype": "Select",
"in_preview": 1,
"label": "Blood Group", "label": "Blood Group",
"options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative" "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative"
}, },
@ -116,6 +124,7 @@
"bold": 1, "bold": 1,
"fieldname": "dob", "fieldname": "dob",
"fieldtype": "Date", "fieldtype": "Date",
"in_preview": 1,
"label": "Date of birth" "label": "Date of birth"
}, },
{ {
@ -142,6 +151,7 @@
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach Image", "fieldtype": "Attach Image",
"hidden": 1, "hidden": 1,
"in_preview": 1,
"label": "Image", "label": "Image",
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
@ -157,7 +167,8 @@
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Customer", "label": "Customer",
"options": "Customer" "options": "Customer",
"set_only_once": 1
}, },
{ {
"fieldname": "report_preference", "fieldname": "report_preference",
@ -171,7 +182,8 @@
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Mobile" "label": "Mobile",
"options": "Phone"
}, },
{ {
"bold": 1, "bold": 1,
@ -186,7 +198,8 @@
"fieldname": "phone", "fieldname": "phone",
"fieldtype": "Data", "fieldtype": "Data",
"in_filter": 1, "in_filter": 1,
"label": "Phone" "label": "Phone",
"options": "Phone"
}, },
{ {
"collapsible": 1, "collapsible": 1,
@ -268,25 +281,25 @@
"fieldname": "tobacco_past_use", "fieldname": "tobacco_past_use",
"fieldtype": "Data", "fieldtype": "Data",
"ignore_xss_filter": 1, "ignore_xss_filter": 1,
"label": "Tobacco Consumption Habbits (Past)" "label": "Tobacco Consumption (Past)"
}, },
{ {
"fieldname": "tobacco_current_use", "fieldname": "tobacco_current_use",
"fieldtype": "Data", "fieldtype": "Data",
"ignore_xss_filter": 1, "ignore_xss_filter": 1,
"label": "Tobacco Consumption Habbits (Present)" "label": "Tobacco Consumption (Present)"
}, },
{ {
"fieldname": "alcohol_past_use", "fieldname": "alcohol_past_use",
"fieldtype": "Data", "fieldtype": "Data",
"ignore_xss_filter": 1, "ignore_xss_filter": 1,
"label": "Alcohol Consumption Habbits (Past)" "label": "Alcohol Consumption (Past)"
}, },
{ {
"fieldname": "alcohol_current_use", "fieldname": "alcohol_current_use",
"fieldtype": "Data", "fieldtype": "Data",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Alcohol Consumption Habbits (Present)" "label": "Alcohol Consumption (Present)"
}, },
{ {
"fieldname": "column_break_32", "fieldname": "column_break_32",
@ -320,20 +333,11 @@
"ignore_xss_filter": 1, "ignore_xss_filter": 1,
"label": "Patient Details" "label": "Patient Details"
}, },
{
"collapsible": 1,
"fieldname": "ac_sb",
"fieldtype": "Section Break",
"label": "Account Details"
},
{ {
"fieldname": "default_currency", "fieldname": "default_currency",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "label": "Billing Currency",
"ignore_xss_filter": 1, "options": "Currency"
"label": "Default Currency",
"options": "Currency",
"print_hide": 1
}, },
{ {
"fieldname": "last_name", "fieldname": "last_name",
@ -351,13 +355,47 @@
"fieldname": "middle_name", "fieldname": "middle_name",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Middle Name (optional)" "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", "icon": "fa fa-user",
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"max_attachments": 50, "max_attachments": 50,
"modified": "2020-04-06 12:55:30.807744", "modified": "2020-04-25 17:24:32.146415",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Patient", "name": "Patient",

View File

@ -10,6 +10,7 @@ from frappe.utils import cint, cstr, getdate
import dateutil import dateutil
from frappe.model.naming import set_name_by_naming_series from frappe.model.naming import set_name_by_naming_series
from frappe.utils.nestedset import get_root_of 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 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account, send_registration_sms
class Patient(Document): class Patient(Document):
@ -17,6 +18,9 @@ class Patient(Document):
self.set_full_name() self.set_full_name()
self.add_as_website_user() self.add_as_website_user()
def before_insert(self):
self.set_missing_customer_details()
def after_insert(self): def after_insert(self):
self.add_as_website_user() self.add_as_website_user()
self.reload() self.reload()
@ -26,6 +30,25 @@ class Patient(Document):
frappe.db.set_value('Patient', self.name, 'status', 'Disabled') frappe.db.set_value('Patient', self.name, 'status', 'Disabled')
else: else:
send_registration_sms(self) 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): def set_full_name(self):
if self.last_name: if self.last_name:
@ -33,6 +56,22 @@ class Patient(Document):
else: else:
self.patient_name = self.first_name 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): def add_as_website_user(self):
if self.email: if self.email:
if not frappe.db.exists ('User', self.email): if not frappe.db.exists ('User', self.email):
@ -86,19 +125,15 @@ class Patient(Document):
return {'invoice': sales_invoice.name} return {'invoice': sales_invoice.name}
def create_customer(doc): 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({ customer = frappe.get_doc({
'doctype': 'Customer', 'doctype': 'Customer',
'customer_name': doc.patient_name, 'customer_name': doc.patient_name,
'customer_group': customer_group, 'customer_group': doc.customer_group or frappe.db.get_single_value('Selling Settings', 'customer_group'),
'territory' : territory, 'territory' : doc.territory or frappe.db.get_single_value('Selling Settings', 'territory'),
'customer_type': 'Individual' '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) }).insert(ignore_permissions=True, ignore_mandatory=True)
frappe.db.set_value('Patient', doc.name, 'customer', customer.name) frappe.db.set_value('Patient', doc.name, 'customer', customer.name)

View File

@ -32,8 +32,9 @@ frappe.ui.form.on('Patient Appointment', {
frm.set_query('service_unit', function(){ frm.set_query('service_unit', function(){
return { return {
filters: { filters: {
'is_group': 0, 'is_group': false,
'allow_appointments': 1 'allow_appointments': true,
'company': frm.doc.company
} }
}; };
}); });
@ -127,6 +128,11 @@ frappe.ui.form.on('Patient Appointment', {
patient: function(frm) { patient: function(frm) {
if (frm.doc.patient) { if (frm.doc.patient) {
frm.trigger('toggle_payment_fields'); 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 @@ let check_and_set_availability = function(frm) {
d.hide(); d.hide();
frm.enable_save(); frm.enable_save();
frm.save(); frm.save();
frm.enable_save();
d.get_primary_btn().attr('disabled', true); d.get_primary_btn().attr('disabled', true);
} }
}); });
@ -481,6 +486,7 @@ let create_vital_signs = function(frm) {
frappe.route_options = { frappe.route_options = {
'patient': frm.doc.patient, 'patient': frm.doc.patient,
'appointment': frm.doc.name, 'appointment': frm.doc.name,
'company': frm.doc.company
}; };
frappe.new_doc('Vital Signs'); frappe.new_doc('Vital Signs');
}; };
@ -513,6 +519,7 @@ frappe.ui.form.on('Patient Appointment', 'practitioner', function(frm) {
callback: function (data) { callback: function (data) {
frappe.model.set_value(frm.doctype, frm.docname, 'department', data.message.department); 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, 'paid_amount', data.message.op_consulting_charge);
frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.op_consulting_charge_item);
} }
}); });
} }

View File

@ -10,40 +10,44 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"naming_series", "naming_series",
"title",
"status",
"patient", "patient",
"patient_name", "patient_name",
"patient_sex", "patient_sex",
"patient_age", "patient_age",
"inpatient_record", "inpatient_record",
"column_break_1", "column_break_1",
"status", "company",
"service_unit",
"procedure_template", "procedure_template",
"get_procedure_from_encounter", "get_procedure_from_encounter",
"procedure_prescription", "procedure_prescription",
"therapy_type", "therapy_type",
"get_prescribed_therapies", "get_prescribed_therapies",
"therapy_plan", "therapy_plan",
"service_unit",
"section_break_12",
"practitioner", "practitioner",
"practitioner_name",
"department", "department",
"section_break_12",
"appointment_type", "appointment_type",
"duration",
"column_break_17", "column_break_17",
"appointment_date", "appointment_date",
"appointment_time", "appointment_time",
"appointment_datetime", "appointment_datetime",
"duration",
"section_break_16", "section_break_16",
"mode_of_payment", "mode_of_payment",
"paid_amount", "billing_item",
"company",
"column_break_2", "column_break_2",
"paid_amount",
"invoiced", "invoiced",
"ref_sales_invoice", "ref_sales_invoice",
"section_break_3", "section_break_3",
"notes",
"referring_practitioner", "referring_practitioner",
"reminded" "reminded",
"column_break_36",
"notes"
], ],
"fields": [ "fields": [
{ {
@ -55,7 +59,6 @@
"read_only": 1 "read_only": 1
}, },
{ {
"fetch_from": "inpatient_record.patient",
"fieldname": "patient", "fieldname": "patient",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
@ -79,7 +82,8 @@
"fieldname": "duration", "fieldname": "duration",
"fieldtype": "Int", "fieldtype": "Int",
"in_filter": 1, "in_filter": 1,
"label": "Duration (In Minutes)" "label": "Duration (In Minutes)",
"set_only_once": 1
}, },
{ {
"fieldname": "column_break_1", "fieldname": "column_break_1",
@ -98,6 +102,7 @@
"search_index": 1 "search_index": 1
}, },
{ {
"depends_on": "eval:doc.patient;",
"fieldname": "procedure_template", "fieldname": "procedure_template",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Clinical Procedure Template", "label": "Clinical Procedure Template",
@ -117,7 +122,8 @@
"label": "Procedure Prescription", "label": "Procedure Prescription",
"no_copy": 1, "no_copy": 1,
"options": "Procedure Prescription", "options": "Procedure Prescription",
"print_hide": 1 "print_hide": 1,
"read_only": 1
}, },
{ {
"fieldname": "service_unit", "fieldname": "service_unit",
@ -128,7 +134,8 @@
}, },
{ {
"fieldname": "section_break_12", "fieldname": "section_break_12",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Appointment Details"
}, },
{ {
"fieldname": "practitioner", "fieldname": "practitioner",
@ -143,6 +150,7 @@
"set_only_once": 1 "set_only_once": 1
}, },
{ {
"fetch_from": "practitioner.department",
"fieldname": "department", "fieldname": "department",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
@ -173,11 +181,13 @@
"fieldtype": "Time", "fieldtype": "Time",
"in_list_view": 1, "in_list_view": 1,
"label": "Time", "label": "Time",
"read_only": 1 "read_only": 1,
"reqd": 1
}, },
{ {
"fieldname": "section_break_16", "fieldname": "section_break_16",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Payments"
}, },
{ {
"fetch_from": "patient.patient_name", "fetch_from": "patient.patient_name",
@ -206,6 +216,7 @@
{ {
"fieldname": "appointment_datetime", "fieldname": "appointment_datetime",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"hidden": 1,
"label": "Appointment Datetime", "label": "Appointment Datetime",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
@ -237,12 +248,12 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "in_standard_filter": 1,
"label": "Company", "label": "Company",
"no_copy": 1, "no_copy": 1,
"options": "Company", "options": "Company",
"print_hide": 1, "reqd": 1,
"report_hide": 1 "set_only_once": 1
}, },
{ {
"collapsible": 1, "collapsible": 1,
@ -307,10 +318,37 @@
"label": "Series", "label": "Series",
"options": "HLC-APP-.YYYY.-", "options": "HLC-APP-.YYYY.-",
"set_only_once": 1 "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": [], "links": [],
"modified": "2020-03-31 16:16:32.116865", "modified": "2020-04-27 21:36:06.404062",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Patient Appointment", "name": "Patient Appointment",
@ -358,7 +396,7 @@
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "patient", "title_field": "title",
"track_changes": 1, "track_changes": 1,
"track_seen": 1 "track_seen": 1
} }

View File

@ -21,6 +21,7 @@ class PatientAppointment(Document):
self.set_appointment_datetime() self.set_appointment_datetime()
self.validate_customer_created() self.validate_customer_created()
self.set_status() self.set_status()
self.set_title()
def after_insert(self): def after_insert(self):
self.update_prescription_details() self.update_prescription_details()
@ -28,6 +29,10 @@ class PatientAppointment(Document):
self.update_fee_validity() self.update_fee_validity()
send_confirmation_msg(self) 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): def set_status(self):
today = getdate() today = getdate()
appointment_date = getdate(self.appointment_date) appointment_date = getdate(self.appointment_date)
@ -119,25 +124,28 @@ def invoice_appointment(appointment_doc):
if automate_invoicing and not appointment_invoiced and not fee_validity: if automate_invoicing and not appointment_invoiced and not fee_validity:
sales_invoice = frappe.new_doc('Sales Invoice') 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.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer')
sales_invoice.appointment = appointment_doc.name sales_invoice.appointment = appointment_doc.name
sales_invoice.due_date = getdate() sales_invoice.due_date = getdate()
sales_invoice.is_pos = 1
sales_invoice.company = appointment_doc.company sales_invoice.company = appointment_doc.company
sales_invoice.debit_to = get_receivable_account(appointment_doc.company) sales_invoice.debit_to = get_receivable_account(appointment_doc.company)
item = sales_invoice.append('items', {}) item = sales_invoice.append('items', {})
item = get_appointment_item(appointment_doc, item) item = get_appointment_item(appointment_doc, item)
payment = sales_invoice.append('payments', {}) # Add payments if payment details are supplied else proceed to create invoice as Unpaid
payment.mode_of_payment = appointment_doc.mode_of_payment if appointment_doc.mode_of_payment and appointment_doc.paid_amount:
payment.amount = 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.set_missing_values(for_validate=True)
sales_invoice.flags.ignore_mandatory = True sales_invoice.flags.ignore_mandatory = True
sales_invoice.save(ignore_permissions=True) sales_invoice.save(ignore_permissions=True)
sales_invoice.submit() 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, 'invoiced', 1)
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name) frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name)
@ -343,8 +351,8 @@ def make_encounter(source_name, target_doc=None):
['practitioner', 'practitioner'], ['practitioner', 'practitioner'],
['medical_department', 'department'], ['medical_department', 'department'],
['patient_sex', 'patient_sex'], ['patient_sex', 'patient_sex'],
['encounter_date', 'appointment_date'], ['invoiced', 'invoiced'],
['invoiced', 'invoiced'] ['company', 'company']
] ]
} }
}, target_doc) }, target_doc)
@ -370,17 +378,19 @@ def send_appointment_reminder():
frappe.db.set_value('Patient Appointment', doc.name, 'reminded', 1) frappe.db.set_value('Patient Appointment', doc.name, 'reminded', 1)
def send_message(doc, message): def send_message(doc, message):
patient = frappe.get_doc('Patient', doc.patient) patient_mobile = frappe.db.get_value('Patient', doc.patient, 'mobile')
if patient.mobile: if patient_mobile:
context = {'doc': doc, 'alert': doc, 'comments': None} context = {'doc': doc, 'alert': doc, 'comments': None}
if doc.get('_comments'): if doc.get('_comments'):
context['comments'] = json.loads(doc.get('_comments')) context['comments'] = json.loads(doc.get('_comments'))
# jinja to string convertion happens here # jinja to string convertion happens here
message = frappe.render_template(message, context) message = frappe.render_template(message, context)
number = [patient.mobile] number = [patient_mobile]
send_sms(number, message) try:
send_sms(number, message)
except Exception as e:
frappe.msgprint(_('SMS not sent, please check SMS Settings'), alert=True)
@frappe.whitelist() @frappe.whitelist()
def get_events(start, end, filters=None): def get_events(start, end, filters=None):

View File

@ -4,14 +4,15 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import frappe 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 import nowdate, add_days
from frappe.utils.make_random import get_random from frappe.utils.make_random import get_random
class TestPatientAppointment(unittest.TestCase): class TestPatientAppointment(unittest.TestCase):
def setUp(self): def setUp(self):
frappe.db.sql("""delete from `tabPatient Appointment`""") 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): def test_status(self):
patient, medical_department, practitioner = create_healthcare_docs() patient, medical_department, practitioner = create_healthcare_docs()
@ -23,6 +24,19 @@ class TestPatientAppointment(unittest.TestCase):
create_encounter(appointment) create_encounter(appointment)
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') 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): def test_invoicing(self):
patient, medical_department, practitioner = create_healthcare_docs() patient, medical_department, practitioner = create_healthcare_docs()
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
@ -33,7 +47,11 @@ class TestPatientAppointment(unittest.TestCase):
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), invoice=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.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): def test_appointment_cancel(self):
patient, medical_department, practitioner = create_healthcare_docs() patient, medical_department, practitioner = create_healthcare_docs()
@ -53,8 +71,8 @@ class TestPatientAppointment(unittest.TestCase):
appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
update_status(appointment.name, 'Cancelled') update_status(appointment.name, 'Cancelled')
# check invoice cancelled # check invoice cancelled
sales_invoice = 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.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice, 'status'), 'Cancelled') self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled')
def create_healthcare_docs(): def create_healthcare_docs():
@ -90,14 +108,15 @@ def create_patient():
patient = patient.name patient = patient.name
return patient return patient
def create_encounter(appointment=None): def create_encounter(appointment):
encounter = frappe.new_doc('Patient Encounter')
if appointment: if appointment:
encounter = frappe.new_doc('Patient Encounter')
encounter.appointment = appointment.name encounter.appointment = appointment.name
encounter.patient = appointment.patient encounter.patient = appointment.patient
encounter.practitioner = appointment.practitioner encounter.practitioner = appointment.practitioner
encounter.encounter_date = appointment.appointment_date encounter.encounter_date = appointment.appointment_date
encounter.encounter_time = appointment.appointment_time encounter.encounter_time = appointment.appointment_time
encounter.company = appointment.company
encounter.save() encounter.save()
encounter.submit() encounter.submit()
return encounter return encounter

View File

@ -25,15 +25,16 @@ frappe.ui.form.on('Patient Encounter', {
refresh_field('lab_test_prescription'); refresh_field('lab_test_prescription');
if (!frm.doc.__islocal) { if (!frm.doc.__islocal) {
if (frm.doc.docstatus === 1) {
if (frm.doc.inpatient_status == 'Admission Scheduled' || frm.doc.inpatient_status == 'Admitted') { if (frm.doc.inpatient_status == 'Admission Scheduled' || frm.doc.inpatient_status == 'Admitted') {
frm.add_custom_button(__('Schedule Discharge'), function() { frm.add_custom_button(__('Schedule Discharge'), function() {
schedule_discharge(frm); schedule_discharge(frm);
}); });
} else if (frm.doc.inpatient_status != 'Discharge Scheduled') { } else if (frm.doc.inpatient_status != 'Discharge Scheduled') {
frm.add_custom_button(__('Schedule Admission'), function() { frm.add_custom_button(__('Schedule Admission'), function() {
schedule_inpatient(frm); schedule_inpatient(frm);
}); });
}
} }
frm.add_custom_button(__('Patient History'), function() { frm.add_custom_button(__('Patient History'), function() {
@ -101,6 +102,11 @@ frappe.ui.form.on('Patient Encounter', {
frm.events.set_patient_info(frm); frm.events.set_patient_info(frm);
}, },
practitioner: function(frm) {
if (!frm.doc.practitioner) {
frm.set_value('practitioner_name', '');
}
},
set_appointment_fields: function(frm) { set_appointment_fields: function(frm) {
if (frm.doc.appointment) { if (frm.doc.appointment) {
frappe.call({ frappe.call({
@ -114,9 +120,11 @@ frappe.ui.form.on('Patient Encounter', {
'patient':data.message.patient, 'patient':data.message.patient,
'type': data.message.appointment_type, 'type': data.message.appointment_type,
'practitioner': data.message.practitioner, 'practitioner': data.message.practitioner,
'invoiced': data.message.invoiced 'invoiced': data.message.invoiced,
'company': data.message.company
}; };
frm.set_value(values); frm.set_value(values);
frm.set_df_property('patient', 'read_only', 1);
} }
}); });
} }
@ -133,6 +141,7 @@ frappe.ui.form.on('Patient Encounter', {
'inpatient_status': '' 'inpatient_status': ''
}; };
frm.set_value(values); frm.set_value(values);
frm.set_df_property('patient', 'read_only', 0);
} }
}, },
@ -148,19 +157,25 @@ frappe.ui.form.on('Patient Encounter', {
if (data.message.dob) { if (data.message.dob) {
age = calculate_age(data.message.dob); age = calculate_age(data.message.dob);
} }
frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', age); let values = {
frappe.model.set_value(frm.doctype, frm.docname, 'patient_sex', data.message.sex); 'patient_age': age,
if (data.message.inpatient_record) { 'patient_name':data.message.patient_name,
frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_record', data.message.inpatient_record); 'patient_sex': data.message.sex,
frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_status', data.message.inpatient_status); 'inpatient_record': data.message.inpatient_record,
} 'inpatient_status': data.message.inpatient_status
};
frm.set_value(values);
} }
}); });
} else { } else {
frappe.model.set_value(frm.doctype, frm.docname, 'patient_sex', ''); let values = {
frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', ''); 'patient_age': '',
frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_record', ''); 'patient_name':'',
frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_status', ''); 'patient_sex': '',
'inpatient_record': '',
'inpatient_status': ''
};
frm.set_value(values);
} }
} }
}); });
@ -212,8 +227,8 @@ let create_vital_signs = function (frm) {
} }
frappe.route_options = { frappe.route_options = {
'patient': frm.doc.patient, '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'); frappe.new_doc('Vital Signs');
}; };
@ -224,7 +239,8 @@ let create_procedure = function (frm) {
} }
frappe.route_options = { frappe.route_options = {
'patient': frm.doc.patient, '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'); frappe.new_doc('Clinical Procedure');
}; };

View File

@ -11,23 +11,23 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"naming_series", "naming_series",
"title",
"appointment", "appointment",
"appointment_type", "appointment_type",
"patient", "patient",
"patient_name", "patient_name",
"patient_sex", "patient_sex",
"patient_age", "patient_age",
"company", "inpatient_record",
"inpatient_status",
"column_break_6", "column_break_6",
"practitioner", "company",
"medical_department",
"encounter_date", "encounter_date",
"encounter_time", "encounter_time",
"practitioner",
"practitioner_name",
"medical_department",
"invoiced", "invoiced",
"section_break_1",
"inpatient_record",
"column_break_17",
"inpatient_status",
"sb_symptoms", "sb_symptoms",
"symptoms", "symptoms",
"symptoms_in_print", "symptoms_in_print",
@ -47,6 +47,7 @@
"therapies", "therapies",
"section_break_33", "section_break_33",
"encounter_comment", "encounter_comment",
"sb_refs",
"amended_from" "amended_from"
], ],
"fields": [ "fields": [
@ -57,12 +58,6 @@
"options": "Inpatient Record", "options": "Inpatient Record",
"read_only": 1 "read_only": 1
}, },
{
"collapsible": 1,
"fieldname": "section_break_1",
"fieldtype": "Section Break",
"label": "Inpatient Details"
},
{ {
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
@ -77,14 +72,13 @@
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Appointment", "label": "Appointment",
"options": "Patient Appointment", "options": "Patient Appointment",
"search_index": 1 "search_index": 1,
"set_only_once": 1
}, },
{ {
"fetch_from": "inpatient_record.patient",
"fieldname": "patient", "fieldname": "patient",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Patient", "label": "Patient",
"options": "Patient", "options": "Patient",
@ -92,7 +86,6 @@
"search_index": 1 "search_index": 1
}, },
{ {
"fetch_from": "patient.patient_name",
"fieldname": "patient_name", "fieldname": "patient_name",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Patient Name", "label": "Patient Name",
@ -114,7 +107,6 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1,
"label": "Company", "label": "Company",
"options": "Company" "options": "Company"
}, },
@ -125,7 +117,6 @@
{ {
"fieldname": "practitioner", "fieldname": "practitioner",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Healthcare Practitioner", "label": "Healthcare Practitioner",
"options": "Healthcare Practitioner", "options": "Healthcare Practitioner",
@ -207,29 +198,29 @@
{ {
"fieldname": "codification_table", "fieldname": "codification_table",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Medical Coding", "label": "Medical Codes",
"options": "Codification Table" "options": "Codification Table"
}, },
{ {
"fieldname": "sb_drug_prescription", "fieldname": "sb_drug_prescription",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Medication" "label": "Medications"
}, },
{ {
"fieldname": "drug_prescription", "fieldname": "drug_prescription",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Drug Prescription", "label": "Items",
"options": "Drug Prescription" "options": "Drug Prescription"
}, },
{ {
"fieldname": "sb_test_prescription", "fieldname": "sb_test_prescription",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Investigation" "label": "Investigations"
}, },
{ {
"fieldname": "lab_test_prescription", "fieldname": "lab_test_prescription",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Lab Prescription", "label": "Lab Tests",
"options": "Lab Prescription" "options": "Lab Prescription"
}, },
{ {
@ -240,7 +231,7 @@
{ {
"fieldname": "procedure_prescription", "fieldname": "procedure_prescription",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Procedure Prescription", "label": "Clinical Procedures",
"no_copy": 1, "no_copy": 1,
"options": "Procedure Prescription" "options": "Procedure Prescription"
}, },
@ -299,7 +290,6 @@
"fieldname": "medical_department", "fieldname": "medical_department",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Department", "label": "Department",
"options": "Medical Department", "options": "Medical Department",
@ -312,13 +302,31 @@
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "column_break_17", "fieldname": "sb_refs",
"fieldtype": "Column Break" "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, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-14 16:18:08.180457", "modified": "2020-04-27 21:58:29.789797",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Patient Encounter", "name": "Patient Encounter",
@ -345,7 +353,7 @@
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "patient", "title_field": "title",
"track_changes": 1, "track_changes": 1,
"track_seen": 1 "track_seen": 1
} }

View File

@ -10,6 +10,9 @@ from frappe.utils import cstr
from frappe import _ from frappe import _
class PatientEncounter(Document): class PatientEncounter(Document):
def validate(self):
self.set_title()
def on_update(self): def on_update(self):
if self.appointment: if self.appointment:
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed') frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
@ -29,6 +32,10 @@ class PatientEncounter(Document):
def on_submit(self): def on_submit(self):
create_therapy_plan(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): def create_therapy_plan(encounter):
if len(encounter.therapies): if len(encounter.therapies):
doc = frappe.new_doc('Therapy Plan') doc = frappe.new_doc('Therapy Plan')

View File

@ -9,14 +9,14 @@
"document_type": "Document", "document_type": "Document",
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"inpatient_record",
"naming_series", "naming_series",
"invoiced",
"patient", "patient",
"column_break_4",
"patient_age", "patient_age",
"patient_sex", "patient_sex",
"column_break_4",
"inpatient_record",
"company", "company",
"invoiced",
"section_break_6", "section_break_6",
"sample", "sample",
"sample_uom", "sample_uom",
@ -167,7 +167,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-03-25 16:55:52.376834", "modified": "2020-04-04 19:17:02.707203",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Sample Collection", "name": "Sample Collection",

View File

@ -9,6 +9,16 @@ frappe.ui.form.on('Therapy Session', {
{fieldname: 'counts_completed', columns: 1}, {fieldname: 'counts_completed', columns: 1},
{fieldname: 'assistance_level', 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) { refresh: function(frm) {

View File

@ -2,18 +2,22 @@
"actions": [], "actions": [],
"allow_copy": 1, "allow_copy": 1,
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:",
"beta": 1, "beta": 1,
"creation": "2017-02-02 11:00:24.853005", "creation": "2017-02-02 11:00:24.853005",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"inpatient_record", "naming_series",
"title",
"patient", "patient",
"patient_name", "patient_name",
"inpatient_record",
"appointment", "appointment",
"encounter", "encounter",
"column_break_2", "column_break_2",
"company",
"signs_date", "signs_date",
"signs_time", "signs_time",
"sb_vs", "sb_vs",
@ -34,7 +38,7 @@
"bmi", "bmi",
"column_break_14", "column_break_14",
"nutrition_note", "nutrition_note",
"company", "sb_references",
"amended_from" "amended_from"
], ],
"fields": [ "fields": [
@ -68,7 +72,8 @@
"fieldname": "appointment", "fieldname": "appointment",
"fieldtype": "Link", "fieldtype": "Link",
"in_filter": 1, "in_filter": 1,
"label": "Appointment", "label": "Patient Appointment",
"no_copy": 1,
"options": "Patient Appointment", "options": "Patient Appointment",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
@ -81,8 +86,7 @@
"no_copy": 1, "no_copy": 1,
"options": "Patient Encounter", "options": "Patient Encounter",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1
"report_hide": 1
}, },
{ {
"fieldname": "column_break_2", "fieldname": "column_break_2",
@ -217,7 +221,6 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1,
"label": "Company", "label": "Company",
"options": "Company" "options": "Company"
}, },
@ -229,11 +232,34 @@
"options": "Vital Signs", "options": "Vital Signs",
"print_hide": 1, "print_hide": 1,
"read_only": 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, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-03-04 17:19:29.549889", "modified": "2020-05-17 22:23:24.632286",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Vital Signs", "name": "Vital Signs",
@ -273,7 +299,7 @@
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "patient", "title_field": "title",
"track_changes": 1, "track_changes": 1,
"track_seen": 1 "track_seen": 1
} }

View File

@ -9,12 +9,19 @@ from frappe.utils import cstr
from frappe import _ from frappe import _
class VitalSigns(Document): class VitalSigns(Document):
def validate(self):
self.set_title()
def on_submit(self): def on_submit(self):
insert_vital_signs_to_medical_record(self) insert_vital_signs_to_medical_record(self)
def on_cancel(self): def on_cancel(self):
delete_vital_signs_from_medical_record(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): def insert_vital_signs_to_medical_record(doc):
subject = set_subject_field(doc) subject = set_subject_field(doc)
medical_record = frappe.new_doc('Patient Medical Record') medical_record = frappe.new_doc('Patient Medical Record')

View File

@ -3,83 +3,84 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import math
import frappe import frappe
from frappe import _ from frappe import _
import math
from frappe.utils import time_diff_in_hours, rounded 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.healthcare_settings.healthcare_settings import get_income_account
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
@frappe.whitelist() @frappe.whitelist()
def get_healthcare_services_to_invoice(patient): def get_healthcare_services_to_invoice(patient, company):
patient = frappe.get_doc('Patient', patient) patient = frappe.get_doc('Patient', patient)
items_to_invoice = []
if patient: if patient:
validate_customer_created(patient) validate_customer_created(patient)
items_to_invoice = [] # Customer validated, build a list of billable services
patient_appointments = frappe.get_list( items_to_invoice += get_appointments_to_invoice(patient, company)
'Patient Appointment', items_to_invoice += get_encounters_to_invoice(patient, company)
fields='*', items_to_invoice += get_lab_tests_to_invoice(patient, company)
filters={'patient': patient.name, 'invoiced': 0}, items_to_invoice += get_clinical_procedures_to_invoice(patient, company)
order_by='appointment_date' items_to_invoice += get_inpatient_services_to_invoice(patient, company)
) items_to_invoice += get_therapy_sessions_to_invoice(patient, company)
if patient_appointments:
items_to_invoice = get_fee_validity(patient_appointments)
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 return items_to_invoice
def validate_customer_created(patient): def validate_customer_created(patient):
if not frappe.db.get_value('Patient', patient.name, 'customer'): if not frappe.db.get_value('Patient', patient.name, 'customer'):
msg = _("Please set a Customer linked to the Patient") msg = _("Please set a Customer linked to the Patient")
msg += " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(patient.name) msg += " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(patient.name)
frappe.throw(msg, title=_('Customer Not Found')) frappe.throw(msg, title=_('Customer Not Found'))
def get_fee_validity(patient_appointments): def get_appointments_to_invoice(patient, company):
if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): appointments_to_invoice = []
return [] 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: for appointment in patient_appointments:
# Procedure Appointments
if appointment.procedure_template: if appointment.procedure_template:
if frappe.db.get_value('Clinical Procedure Template', appointment.procedure_template, 'is_billable'): 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_type': 'Patient Appointment',
'reference_name': appointment.name, 'reference_name': appointment.name,
'service': appointment.procedure_template 'service': appointment.procedure_template
}) })
# Consultation Appointments, should check fee validity
else: else:
fee_validity = frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}) if frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') and \
if not fee_validity: frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}):
practitioner_charge = 0 continue # Skip invoicing, fee validty present
income_account = None practitioner_charge = 0
service_item = None income_account = None
if appointment.practitioner: service_item = None
service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment) if appointment.practitioner:
income_account = get_income_account(appointment.practitioner, appointment.company) service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment)
items_to_invoice.append({ income_account = get_income_account(appointment.practitioner, appointment.company)
'reference_type': 'Patient Appointment', appointments_to_invoice.append({
'reference_name': appointment.name, 'reference_type': 'Patient Appointment',
'service': service_item, 'reference_name': appointment.name,
'rate': practitioner_charge, 'service': service_item,
'income_account': income_account '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_to_invoice = []
encounters = frappe.get_list( encounters = frappe.get_list(
'Patient Encounter', 'Patient Encounter',
fields=['*'], fields=['*'],
filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1}
) )
if encounters: if encounters:
for encounter in encounters: for encounter in encounters:
@ -102,12 +103,12 @@ def get_encounters_to_invoice(patient):
return encounters_to_invoice 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_to_invoice = []
lab_tests = frappe.get_list( lab_tests = frappe.get_list(
'Lab Test', 'Lab Test',
fields=['name', 'template'], 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: for lab_test in lab_tests:
item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.template, ['item', 'is_billable']) item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.template, ['item', 'is_billable'])
@ -143,12 +144,12 @@ def get_lab_tests_to_invoice(patient):
return lab_tests_to_invoice return lab_tests_to_invoice
def get_clinical_procedures_to_invoice(patient): def get_clinical_procedures_to_invoice(patient, company):
clinical_procedures_to_invoice = [] clinical_procedures_to_invoice = []
procedures = frappe.get_list( procedures = frappe.get_list(
'Clinical Procedure', 'Clinical Procedure',
fields='*', fields='*',
filters={'patient': patient.name, 'invoiced': False} filters={'patient': patient.name, 'company': company, 'invoiced': False}
) )
for procedure in procedures: for procedure in procedures:
if not procedure.appointment: if not procedure.appointment:
@ -204,7 +205,7 @@ def get_clinical_procedures_to_invoice(patient):
return clinical_procedures_to_invoice return clinical_procedures_to_invoice
def get_inpatient_services_to_invoice(patient): def get_inpatient_services_to_invoice(patient, company):
services_to_invoice = [] services_to_invoice = []
inpatient_services = frappe.db.sql( inpatient_services = frappe.db.sql(
''' '''
@ -214,10 +215,11 @@ def get_inpatient_services_to_invoice(patient):
`tabInpatient Record` ip, `tabInpatient Occupancy` io `tabInpatient Record` ip, `tabInpatient Occupancy` io
WHERE WHERE
ip.patient=%s ip.patient=%s
and ip.company=%s
and io.parent=ip.name and io.parent=ip.name
and io.left=1 and io.left=1
and io.invoiced=0 and io.invoiced=0
''', (patient.name), as_dict=1) ''', (patient.name, company), as_dict=1)
for inpatient_occupancy in inpatient_services: for inpatient_occupancy in inpatient_services:
service_unit_type = frappe.db.get_value('Healthcare Service Unit', inpatient_occupancy.service_unit, 'service_unit_type') service_unit_type = frappe.db.get_value('Healthcare Service Unit', inpatient_occupancy.service_unit, 'service_unit_type')
@ -244,12 +246,12 @@ def get_inpatient_services_to_invoice(patient):
return services_to_invoice 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_to_invoice = []
therapy_sessions = frappe.get_list( therapy_sessions = frappe.get_list(
'Therapy Session', 'Therapy Session',
fields='*', fields='*',
filters={'patient': patient.name, 'invoiced': False} filters={'patient': patient.name, 'invoiced': 0, 'company': company}
) )
for therapy in therapy_sessions: for therapy in therapy_sessions:
if not therapy.appointment: if not therapy.appointment:
@ -396,6 +398,7 @@ def check_fee_validity(appointment):
def manage_fee_validity(appointment): def manage_fee_validity(appointment):
fee_validity = check_fee_validity(appointment) fee_validity = check_fee_validity(appointment)
if fee_validity: if fee_validity:
if appointment.status == 'Cancelled' and fee_validity.visited > 0: if appointment.status == 'Cancelled' and fee_validity.visited > 0:
fee_validity.visited -= 1 fee_validity.visited -= 1

View File

@ -308,7 +308,8 @@ scheduler_events = {
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts", "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.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
"erpnext.selling.doctype.quotation.quotation.set_expired_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": [ "daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.setup.doctype.email_digest.email_digest.send",

View File

@ -21,7 +21,7 @@ class Attendance(Document):
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining") date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
# leaves can be marked for future dates # 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")) frappe.throw(_("Attendance can not be marked for future dates"))
elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining): 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")) frappe.throw(_("Attendance date can not be less than employee's joining date"))
@ -41,7 +41,7 @@ class Attendance(Document):
leave_record = frappe.db.sql(""" leave_record = frappe.db.sql("""
select leave_type, half_day, half_day_date select leave_type, half_day, half_day_date
from `tabLeave Application` from `tabLeave Application`
where employee = %s where employee = %s
and %s between from_date and to_date and %s between from_date and to_date
and status = 'Approved' and status = 'Approved'
and docstatus = 1 and docstatus = 1

View File

@ -14,6 +14,8 @@
"is_group", "is_group",
"disabled", "disabled",
"section_break_4", "section_break_4",
"payroll_cost_center",
"column_break_9",
"leave_block_list", "leave_block_list",
"leave_section", "leave_section",
"leave_approvers", "leave_approvers",
@ -125,13 +127,23 @@
{ {
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break" "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", "icon": "fa fa-sitemap",
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-03-18 18:03:27.784362", "modified": "2020-05-05 18:49:28.503931",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Department", "name": "Department",

View File

@ -60,6 +60,8 @@
"default_shift", "default_shift",
"salary_information", "salary_information",
"salary_mode", "salary_mode",
"payroll_cost_center",
"column_break_52",
"bank_name", "bank_name",
"bank_ac_no", "bank_ac_no",
"health_insurance_section", "health_insurance_section",
@ -783,13 +785,25 @@
{ {
"fieldname": "column_break_19", "fieldname": "column_break_19",
"fieldtype": "Column Break" "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", "icon": "fa fa-user",
"idx": 24, "idx": 24,
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"modified": "2020-04-08 12:25:34.306695", "modified": "2020-05-05 18:51:03.152503",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee", "name": "Employee",

View File

@ -45,7 +45,7 @@ class TestEmployee(unittest.TestCase):
employee1_doc.status = 'Left' employee1_doc.status = 'Left'
self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) 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): if not frappe.db.get_value("User", user):
frappe.get_doc({ frappe.get_doc({
"doctype": "User", "doctype": "User",
@ -55,7 +55,7 @@ def make_employee(user, company=None):
"roles": [{"doctype": "Has Role", "role": "Employee"}] "roles": [{"doctype": "Has Role", "role": "Employee"}]
}).insert() }).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({ employee = frappe.get_doc({
"doctype": "Employee", "doctype": "Employee",
"naming_series": "EMP-", "naming_series": "EMP-",
@ -71,7 +71,10 @@ def make_employee(user, company=None):
"prefered_email": user, "prefered_email": user,
"status": "Active", "status": "Active",
"employment_type": "Intern" "employment_type": "Intern"
}).insert() })
if kwargs:
employee.update(kwargs)
employee.insert()
return employee.name return employee.name
else: else:
return frappe.get_value("Employee", {"employee_name":user}, "name") return frappe.get_value("Employee", {"employee_name":user}, "name")

View File

@ -76,25 +76,15 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-03-19 18:06:45.361830", "modified": "2020-05-14 17:17:38.883126",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Other Income", "name": "Employee Other Income",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"create": 1, "amend": 1,
"delete": 1, "cancel": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@ -104,9 +94,12 @@
"report": 1, "report": 1,
"role": "HR Manager", "role": "HR Manager",
"share": 1, "share": 1,
"submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 1,
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@ -116,9 +109,12 @@
"report": 1, "report": 1,
"role": "HR User", "role": "HR User",
"share": 1, "share": 1,
"submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 1,
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@ -128,6 +124,7 @@
"report": 1, "report": 1,
"role": "Employee", "role": "Employee",
"share": 1, "share": 1,
"submit": 1,
"write": 1 "write": 1
} }
], ],

View File

@ -116,8 +116,9 @@ class ExpenseClaim(AccountsController):
"party_type": "Employee", "party_type": "Employee",
"party": self.employee, "party": self.employee,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"against_voucher": self.name "against_voucher": self.name,
}) "cost_center": self.cost_center
}, item=self)
) )
# expense entries # expense entries
@ -129,7 +130,7 @@ class ExpenseClaim(AccountsController):
"debit_in_account_currency": data.sanctioned_amount, "debit_in_account_currency": data.sanctioned_amount,
"against": self.employee, "against": self.employee,
"cost_center": data.cost_center "cost_center": data.cost_center
}) }, item=data)
) )
for data in self.advances: for data in self.advances:
@ -157,7 +158,7 @@ class ExpenseClaim(AccountsController):
"credit": self.grand_total, "credit": self.grand_total,
"credit_in_account_currency": self.grand_total, "credit_in_account_currency": self.grand_total,
"against": self.employee "against": self.employee
}) }, item=self)
) )
gl_entry.append( gl_entry.append(
@ -170,7 +171,7 @@ class ExpenseClaim(AccountsController):
"debit_in_account_currency": self.grand_total, "debit_in_account_currency": self.grand_total,
"against_voucher": self.name, "against_voucher": self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
}) }, item=self)
) )
return gl_entry return gl_entry
@ -187,7 +188,7 @@ class ExpenseClaim(AccountsController):
"cost_center": self.cost_center, "cost_center": self.cost_center,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"against_voucher": self.name "against_voucher": self.name
}) }, item=tax)
) )
def validate_account_details(self): def validate_account_details(self):

View File

@ -13,9 +13,11 @@
"description", "description",
"section_break_6", "section_break_6",
"amount", "amount",
"cost_center",
"column_break_8", "column_break_8",
"sanctioned_amount" "sanctioned_amount",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
], ],
"fields": [ "fields": [
{ {
@ -104,12 +106,21 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Cost Center", "label": "Cost Center",
"options": "Cost Center" "options": "Cost Center"
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2019-12-11 13:42:33.233432", "modified": "2020-05-11 18:54:35.601592",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Expense Claim Detail", "name": "Expense Claim Detail",

View File

@ -8,14 +8,16 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"account_head", "account_head",
"cost_center",
"rate", "rate",
"col_break1", "col_break1",
"description", "description",
"section_break_6", "section_break_6",
"tax_amount", "tax_amount",
"column_break_8", "column_break_8",
"total" "total",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
], ],
"fields": [ "fields": [
{ {
@ -91,11 +93,20 @@
{ {
"fieldname": "column_break_8", "fieldname": "column_break_8",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
} }
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-03-11 13:25:06.721917", "modified": "2020-05-11 19:01:26.611758",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Expense Taxes and Charges", "name": "Expense Taxes and Charges",

View File

@ -549,7 +549,7 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry):
return _get_remaining_leaves(total_leaves, allocation.to_date) 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_entries = get_leave_entries(employee, leave_type, from_date, to_date)
leave_days = 0 leave_days = 0
@ -559,8 +559,8 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date):
if inclusive_period and leave_entry.transaction_type == 'Leave Encashment': if inclusive_period and leave_entry.transaction_type == 'Leave Encashment':
leave_days += leave_entry.leaves leave_days += leave_entry.leaves
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \ elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' and leave_entry.is_expired \
and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date): and (do_not_skip_expired_leaves or not skip_expiry_leaves(leave_entry, to_date)):
leave_days += leave_entry.leaves leave_days += leave_entry.leaves
elif leave_entry.transaction_type == 'Leave Application': elif leave_entry.transaction_type == 'Leave Application':

View File

@ -88,32 +88,40 @@ def get_previous_expiry_ledger_entry(ledger):
}, fieldname=['name']) }, fieldname=['name'])
def process_expired_allocation(): 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 # fetch leave type records that has carry forwarded leaves expiry
leave_type_records = frappe.db.get_values("Leave Type", filters={ leave_type_records = frappe.db.get_values("Leave Type", filters={
'expire_carry_forwarded_leaves_after_days': (">", 0) 'expire_carry_forwarded_leaves_after_days': (">", 0)
}, fieldname=['name']) }, 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 # fetch non expired leave ledger entry of transaction_type allocation
FROM `tabLeave Ledger Entry` expire_allocation = frappe.db.sql("""
WHERE SELECT
`transaction_type`='Leave Allocation' leaves, to_date, employee, leave_type,
AND `is_expired`=1""") is_carry_forward, transaction_name as name, transaction_type
FROM `tabLeave Ledger Entry` l
expire_allocation = frappe.get_all("Leave Ledger Entry", WHERE (NOT EXISTS
fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'], (SELECT name
filters={ FROM `tabLeave Ledger Entry`
'to_date': ("<", today()), WHERE
'transaction_type': 'Leave Allocation', transaction_name = l.transaction_name
'transaction_name': ('not in', expired_allocation) AND transaction_type = 'Leave Allocation'
}, AND name<>l.name
or_filters={ AND docstatus = 1
'is_carry_forward': 0, AND (
'leave_type': ('in', leave_type) 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: if expire_allocation:
create_expiry_ledger_entry(expire_allocation) create_expiry_ledger_entry(expire_allocation)
@ -133,6 +141,7 @@ def get_remaining_leaves(allocation):
'employee': allocation.employee, 'employee': allocation.employee,
'leave_type': allocation.leave_type, 'leave_type': allocation.leave_type,
'to_date': ('<=', allocation.to_date), 'to_date': ('<=', allocation.to_date),
'docstatus': 1
}, fieldname=['SUM(leaves)']) }, fieldname=['SUM(leaves)'])
@frappe.whitelist() @frappe.whitelist()
@ -159,7 +168,8 @@ def expire_allocation(allocation, expiry_date=None):
def expire_carried_forward_allocation(allocation): def expire_carried_forward_allocation(allocation):
''' Expires remaining leaves in the on carried forward allocation ''' ''' Expires remaining leaves in the on carried forward allocation '''
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period 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) leaves = flt(allocation.leaves) + flt(leaves_taken)
# allow expired leaves entry to be created # allow expired leaves entry to be created

View File

@ -55,6 +55,7 @@ class PayrollEntry(Document):
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
{condition}""".format(condition=condition), {condition}""".format(condition=condition),
{"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) {"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
if sal_struct: if sal_struct:
cond += "and t2.salary_structure IN %(sal_struct)s " cond += "and t2.salary_structure IN %(sal_struct)s "
cond += "and %(from_date)s >= t2.from_date" cond += "and %(from_date)s >= t2.from_date"
@ -138,7 +139,7 @@ class PayrollEntry(Document):
cond = self.get_filter_condition() cond = self.get_filter_condition()
ss_list = frappe.db.sql(""" 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 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 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) """ % ('%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 @@ class PayrollEntry(Document):
def get_salary_components(self, component_type): def get_salary_components(self, component_type):
salary_slips = self.get_sal_slip_list(ss_status = 1, as_dict = True) salary_slips = self.get_sal_slip_list(ss_status = 1, as_dict = True)
if salary_slips: if salary_slips:
salary_components = frappe.db.sql("""select salary_component, amount, parentfield salary_components = frappe.db.sql("""
from `tabSalary Detail` where parentfield = '%s' and parent in (%s)""" % select ssd.salary_component, ssd.amount, ssd.parentfield, ss.payroll_cost_center
(component_type, ', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=True) 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 return salary_components
def get_salary_component_total(self, component_type = None): def get_salary_component_total(self, component_type = None):
@ -186,15 +191,16 @@ class PayrollEntry(Document):
if is_flexible_benefit == 1 and only_tax_impact ==1: if is_flexible_benefit == 1 and only_tax_impact ==1:
add_component_to_accrual_jv_entry = False add_component_to_accrual_jv_entry = False
if add_component_to_accrual_jv_entry: 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) account_details = self.get_account(component_dict = component_dict)
return account_details return account_details
def get_account(self, component_dict = None): def get_account(self, component_dict = None):
account_dict = {} account_dict = {}
for s, a in component_dict.items(): for key, amount in component_dict.items():
account = self.get_salary_component_account(s) account = self.get_salary_component_account(key[0])
account_dict[account] = account_dict.get(account, 0) + a account_dict[(account, key[1])] = account_dict.get((account, key[1]), 0) + amount
return account_dict return account_dict
def get_default_payroll_payable_account(self): def get_default_payroll_payable_account(self):
@ -227,23 +233,23 @@ class PayrollEntry(Document):
payable_amount = 0 payable_amount = 0
# Earnings # Earnings
for acc, amount in earnings.items(): for acc_cc, amount in earnings.items():
payable_amount += flt(amount, precision) payable_amount += flt(amount, precision)
accounts.append({ accounts.append({
"account": acc, "account": acc_cc[0],
"debit_in_account_currency": flt(amount, precision), "debit_in_account_currency": flt(amount, precision),
"party_type": '', "party_type": '',
"cost_center": self.cost_center, "cost_center": acc_cc[1] or self.cost_center,
"project": self.project "project": self.project
}) })
# Deductions # Deductions
for acc, amount in deductions.items(): for acc_cc, amount in deductions.items():
payable_amount -= flt(amount, precision) payable_amount -= flt(amount, precision)
accounts.append({ accounts.append({
"account": acc, "account": acc_cc[0],
"credit_in_account_currency": flt(amount, precision), "credit_in_account_currency": flt(amount, precision),
"cost_center": self.cost_center, "cost_center": acc_cc[1] or self.cost_center,
"party_type": '', "party_type": '',
"project": self.project "project": self.project
}) })
@ -253,6 +259,7 @@ class PayrollEntry(Document):
"account": default_payroll_payable_account, "account": default_payroll_payable_account,
"credit_in_account_currency": flt(payable_amount, precision), "credit_in_account_currency": flt(payable_amount, precision),
"party_type": '', "party_type": '',
"cost_center": self.cost_center
}) })
journal_entry.set("accounts", accounts) journal_entry.set("accounts", accounts)

View File

@ -10,15 +10,16 @@ from frappe.utils import add_months
from erpnext.hr.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date 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.employee.test_employee import make_employee
from erpnext.hr.doctype.salary_slip.test_salary_slip import get_salary_component_account, \ 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.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.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 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): class TestPayrollEntry(unittest.TestCase):
def setUp(self): def setUp(self):
for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry", "Salary Structure"]: for dt in ["Salary Slip", "Salary Component", "Salary Component Account",
frappe.db.sql("delete from `tab%s`" % dt) "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_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_salary_component(setup=True, company_list=["_Test Company"]) make_deduction_salary_component(setup=True, company_list=["_Test Company"])
@ -33,11 +34,59 @@ class TestPayrollEntry(unittest.TestCase):
get_salary_component_account(data.name) get_salary_component_account(data.name)
employee = frappe.db.get_value("Employee", {'company': company}) 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()) 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}): 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) 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): 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-01-01', 'monthly'), {'end_date': '2017-01-31'})
self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'}) self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'})
@ -49,7 +98,6 @@ class TestPayrollEntry(unittest.TestCase):
self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'}) self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'})
def test_loan(self): def test_loan(self):
branch = "Test Employee Branch" branch = "Test Employee Branch"
applicant = make_employee("test_employee@loan.com", company="_Test Company") applicant = make_employee("test_employee@loan.com", company="_Test Company")
company = "_Test Company" company = "_Test Company"
@ -116,6 +164,7 @@ def make_payroll_entry(**args):
payroll_entry.posting_date = nowdate() payroll_entry.posting_date = nowdate()
payroll_entry.payroll_frequency = "Monthly" payroll_entry.payroll_frequency = "Monthly"
payroll_entry.branch = args.branch or None payroll_entry.branch = args.branch or None
payroll_entry.department = args.department or None
if args.cost_center: if args.cost_center:
payroll_entry.cost_center = args.cost_center payroll_entry.cost_center = args.cost_center
@ -123,6 +172,7 @@ def make_payroll_entry(**args):
if args.payment_account: if args.payment_account:
payroll_entry.payment_account = args.payment_account payroll_entry.payment_account = args.payment_account
payroll_entry.fill_employee_details()
payroll_entry.save() payroll_entry.save()
payroll_entry.create_salary_slips() payroll_entry.create_salary_slips()
payroll_entry.submit_salary_slips() payroll_entry.submit_salary_slips()

View File

@ -12,6 +12,7 @@
"department", "department",
"designation", "designation",
"branch", "branch",
"payroll_cost_center",
"column_break1", "column_break1",
"status", "status",
"journal_entry", "journal_entry",
@ -459,13 +460,22 @@
"options": "Salary Slip", "options": "Salary Slip",
"print_hide": 1, "print_hide": 1,
"read_only": 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", "icon": "fa fa-file-text",
"idx": 9, "idx": 9,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-14 20:02:53.159827", "modified": "2020-05-05 18:55:26.173629",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Slip", "name": "Salary Slip",

View File

@ -422,22 +422,32 @@ def get_salary_component_account(sal_comp, company_list=None):
sal_comp = frappe.get_doc("Salary Component", sal_comp) sal_comp = frappe.get_doc("Salary Component", sal_comp)
if not sal_comp.get("accounts"): if not sal_comp.get("accounts"):
for d in company_list: 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", { sal_comp.append("accounts", {
"company": d, "company": d,
"default_account": create_account(d) "default_account": create_account(account_name, d, parent_account)
}) })
sal_comp.save() sal_comp.save()
def create_account(company): def create_account(account_name, company, parent_account):
salary_account = frappe.db.get_value("Account", "Salary - " + frappe.get_cached_value('Company', company, 'abbr')) company_abbr = frappe.get_cached_value('Company', company, 'abbr')
if not salary_account: account = frappe.db.get_value("Account", account_name + " - " + company_abbr)
if not account:
frappe.get_doc({ frappe.get_doc({
"doctype": "Account", "doctype": "Account",
"account_name": "Salary", "account_name": account_name,
"parent_account": "Indirect Expenses - " + frappe.get_cached_value('Company', company, 'abbr'), "parent_account": parent_account,
"company": company "company": company
}).insert() }).insert()
return salary_account return account
def make_earning_salary_component(setup=False, test_tax=False, company_list=None): def make_earning_salary_component(setup=False, test_tax=False, company_list=None):
data = [ data = [
@ -683,7 +693,7 @@ def setup_test():
make_earning_salary_component(setup=True, company_list=["_Test Company"]) make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_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) frappe.db.sql("delete from `tab%s`" % dt)
make_holiday_list() make_holiday_list()

View File

@ -153,12 +153,16 @@ def make_salary_slip(source_name, target_doc = None, employee = None, as_print =
def postprocess(source, target): def postprocess(source, target):
if employee: if employee:
employee_details = frappe.db.get_value("Employee", 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 = employee
target.employee_name = employee_details.employee_name target.employee_name = employee_details.employee_name
target.branch = employee_details.branch target.branch = employee_details.branch
target.designation = employee_details.designation target.designation = employee_details.designation
target.department = employee_details.department 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) target.run_method('process_salary_structure', for_preview=for_preview)
doc = get_mapped_doc("Salary Structure", source_name, { doc = get_mapped_doc("Salary Structure", source_name, {

View File

@ -128,6 +128,7 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do
salary_structure_doc.insert() salary_structure_doc.insert()
if not dont_submit: if not dont_submit:
salary_structure_doc.submit() salary_structure_doc.submit()
else: else:
salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure) salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure)

View File

@ -38,7 +38,7 @@ class LoanSecurityPledge(Document):
for pledge in self.securities: for pledge in self.securities:
if not pledge.qty and not pledge.amount: 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): if not (self.loan_application and pledge.loan_security_price):
pledge.loan_security_price = get_loan_security_price(pledge.loan_security) pledge.loan_security_price = get_loan_security_price(pledge.loan_security)

View File

@ -212,6 +212,12 @@ frappe.ui.form.on("BOM", {
}); });
}, },
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) { routing: function(frm) {
if (frm.doc.routing) { if (frm.doc.routing) {
frappe.call({ frappe.call({
@ -242,7 +248,7 @@ erpnext.bom.BomController = erpnext.TransactionController.extend({
item_code: function(doc, cdt, cdn){ item_code: function(doc, cdt, cdn){
var scrap_items = false; var scrap_items = false;
var child = locals[cdt][cdn]; var child = locals[cdt][cdn];
if(child.doctype == 'BOM Scrap Item') { if (child.doctype == 'BOM Scrap Item') {
scrap_items = true; scrap_items = true;
} }
@ -252,8 +258,19 @@ erpnext.bom.BomController = erpnext.TransactionController.extend({
get_bom_material_detail(doc, cdt, cdn, scrap_items); 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) { 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); var item = frappe.get_doc(cdt, cdn);
frappe.model.round_floats_in(item, ["qty", "conversion_factor"]); frappe.model.round_floats_in(item, ["qty", "conversion_factor"]);
item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item)); item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item));

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"creation": "2013-01-22 15:11:38", "creation": "2013-01-22 15:11:38",
"doctype": "DocType", "doctype": "DocType",
@ -6,23 +7,25 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"item", "item",
"quantity", "company",
"set_rate_of_sub_assembly_item_based_on_bom", "item_name",
"uom",
"cb0", "cb0",
"is_active", "is_active",
"is_default", "is_default",
"allow_alternative_item", "allow_alternative_item",
"image", "set_rate_of_sub_assembly_item_based_on_bom",
"item_name",
"uom",
"currency_detail",
"company",
"project", "project",
"quantity",
"image",
"currency_detail",
"currency",
"conversion_rate", "conversion_rate",
"column_break_12", "column_break_12",
"currency",
"rm_cost_as_per", "rm_cost_as_per",
"buying_price_list", "buying_price_list",
"price_list_currency",
"plc_conversion_rate",
"section_break_21", "section_break_21",
"with_operations", "with_operations",
"column_break_23", "column_break_23",
@ -176,7 +179,8 @@
}, },
{ {
"fieldname": "currency_detail", "fieldname": "currency_detail",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Currency and Price List"
}, },
{ {
"fieldname": "company", "fieldname": "company",
@ -324,7 +328,7 @@
}, },
{ {
"fieldname": "base_scrap_material_cost", "fieldname": "base_scrap_material_cost",
"fieldtype": "Data", "fieldtype": "Currency",
"label": "Scrap Material Cost(Company Currency)", "label": "Scrap Material Cost(Company Currency)",
"no_copy": 1, "no_copy": 1,
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
@ -477,13 +481,31 @@
{ {
"fieldname": "column_break_52", "fieldname": "column_break_52",
"fieldtype": "Column Break" "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", "icon": "fa fa-sitemap",
"idx": 1, "idx": 1,
"image_field": "image", "image_field": "image",
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-11-22 14:35:12.142150", "links": [],
"modified": "2020-05-05 14:29:32.634952",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM", "name": "BOM",

View File

@ -70,11 +70,13 @@ class BOM(WebsiteGenerator):
self.validate_main_item() self.validate_main_item()
self.validate_currency() self.validate_currency()
self.set_conversion_rate() self.set_conversion_rate()
self.set_plc_conversion_rate()
self.validate_uom_is_interger() self.validate_uom_is_interger()
self.set_bom_material_details() self.set_bom_material_details()
self.validate_materials() self.validate_materials()
self.validate_operations() self.validate_operations()
self.calculate_cost() self.calculate_cost()
self.update_cost(update_parent=False, from_child_bom=True, save=False)
def get_context(self, context): def get_context(self, context):
context.parents = [{'name': 'boms', 'title': _('All BOMs') }] context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
@ -165,7 +167,7 @@ class BOM(WebsiteGenerator):
'rate' : rate, 'rate' : rate,
'qty' : args.get("qty") or args.get("stock_qty") or 1, 'qty' : args.get("qty") or args.get("stock_qty") or 1,
'stock_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 'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0
} }
@ -226,7 +228,7 @@ class BOM(WebsiteGenerator):
frappe.msgprint(_("{0} not found for item {1}") frappe.msgprint(_("{0} not found for item {1}")
.format(self.rm_cost_as_per, arg["item_code"]), alert=True) .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): def update_cost(self, update_parent=True, from_child_bom=False, save=True):
if self.docstatus == 2: if self.docstatus == 2:
@ -243,10 +245,15 @@ class BOM(WebsiteGenerator):
"stock_uom": d.stock_uom, "stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor "conversion_factor": d.conversion_factor
}) })
if rate: if rate:
d.rate = rate d.rate = rate
d.amount = flt(d.rate) * flt(d.qty) 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: if self.docstatus == 1:
self.flags.ignore_validate_update_after_submit = True self.flags.ignore_validate_update_after_submit = True
@ -372,6 +379,13 @@ class BOM(WebsiteGenerator):
elif self.conversion_rate == 1 or flt(self.conversion_rate) <= 0: 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") 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): def validate_materials(self):
""" Validate raw material entries """ """ Validate raw material entries """

View File

@ -81,13 +81,13 @@ class TestBOM(unittest.TestCase):
# test amounts in selected currency # test amounts in selected currency
self.assertEqual(bom.operating_cost, 100) self.assertEqual(bom.operating_cost, 100)
self.assertEqual(bom.raw_material_cost, 8000) self.assertEqual(bom.raw_material_cost, 351.68)
self.assertEqual(bom.total_cost, 8100) self.assertEqual(bom.total_cost, 451.68)
# test amounts in selected currency # test amounts in selected currency
self.assertEqual(bom.base_operating_cost, 6000) self.assertEqual(bom.base_operating_cost, 6000)
self.assertEqual(bom.base_raw_material_cost, 480000) self.assertEqual(bom.base_raw_material_cost, 21100.80)
self.assertEqual(bom.base_total_cost, 486000) self.assertEqual(bom.base_total_cost, 27100.80)
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): 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) frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)

View File

@ -623,7 +623,7 @@ erpnext.patches.v11_1.update_default_supplier_in_item_defaults
erpnext.patches.v12_0.update_due_date_in_gle erpnext.patches.v12_0.update_due_date_in_gle
erpnext.patches.v12_0.add_default_buying_selling_terms_in_company erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
erpnext.patches.v12_0.update_ewaybill_field_position 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.v11_1.set_status_for_material_request_type_manufacture
erpnext.patches.v12_0.move_plaid_settings_to_doctype erpnext.patches.v12_0.move_plaid_settings_to_doctype
execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_link') execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_link')
@ -678,6 +678,12 @@ erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
erpnext.patches.v12_0.fix_quotation_expired_status erpnext.patches.v12_0.fix_quotation_expired_status
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype 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 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.delete_doc_if_exists("Page", "appointment-analytic")
execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) 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')

View File

@ -20,7 +20,8 @@ def execute():
else: else:
insert_after_field = 'accounting_dimensions_section' 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}) field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})

View File

@ -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

View File

@ -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)

View File

@ -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""")

View File

@ -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()

View File

@ -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)

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