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

View File

View File

@ -14,7 +14,7 @@ from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist()
@cache_source
def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
to_date = None, timespan = None, time_interval = None):
to_date = None, timespan = None, time_interval = None, heatmap_year = None):
if chart_name:
chart = frappe.get_doc('Dashboard Chart', chart_name)
else:

View File

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

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

View File

@ -45,11 +45,6 @@
"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]"
},
{
"hidden": 0,
"links": "[\n {\n \"description\": \"Match non-linked Invoices and Payments.\",\n \"label\": \"Match Payments with Invoices\",\n \"name\": \"Payment Reconciliation\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update bank payment dates with journals.\",\n \"label\": \"Update Bank Clearance Dates\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Invoice Discounting\",\n \"name\": \"Invoice Discounting\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Clearance Summary\",\n \"name\": \"Bank Clearance Summary\",\n \"type\": \"report\"\n },\n {\n \"label\": \"Bank Guarantee\",\n \"name\": \"Bank Guarantee\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup cheque dimensions for printing\",\n \"label\": \"Cheque Print Template\",\n \"name\": \"Cheque Print Template\",\n \"type\": \"doctype\"\n }\n]",
"title": "Banking and Payments"
},
{
"hidden": 0,
"label": "Subscription Management",
@ -89,8 +84,8 @@
"category": "Modules",
"charts": [
{
"chart_name": "Bank Balance",
"label": "Bank Balance"
"chart_name": "Profit and Loss",
"label": "Profit and Loss"
}
],
"creation": "2020-03-02 15:41:59.515192",
@ -99,23 +94,38 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"icon": "",
"idx": 0,
"is_standard": 1,
"label": "Accounting",
"modified": "2020-04-29 12:17:34.844397",
"modified": "2020-05-18 17:27:26.882340",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
"onboarding": "Accounts",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
"label": "Account",
"label": "Chart Of Accounts",
"link_to": "Account",
"type": "DocType"
},
{
"label": "Sales Invoice",
"link_to": "Sales Invoice",
"type": "DocType"
},
{
"label": "Purchase Invoice",
"link_to": "Purchase Invoice",
"type": "DocType"
},
{
"label": "Accounts Dashboard",
"link_to": "Accounts Dashboard",
"type": "Dashboard"
},
{
"label": "Journal Entry",
"link_to": "Journal Entry",
@ -136,11 +146,6 @@
"link_to": "General Ledger",
"type": "Report"
},
{
"label": "Profit and Loss Statement",
"link_to": "Profit and Loss Statement",
"type": "Report"
},
{
"label": "Trial Balance",
"link_to": "Trial Balance",

View File

@ -162,9 +162,9 @@ def toggle_disabling(doc):
def get_doctypes_with_dimensions():
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
"Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item",
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item",
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
"Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"]

View File

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

View File

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

View File

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

View File

@ -99,7 +99,7 @@ class PricingRule(Document):
self.same_item = 1
def validate_max_discount(self):
if self.rate_or_discount == "Discount Percentage" and self.items:
if self.rate_or_discount == "Discount Percentage" and self.get("items"):
for d in self.items:
max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount")
if max_discount and flt(self.discount_percentage) > flt(max_discount):

View File

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

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_type": self.doctype,
"cost_center": self.cost_center
}, self.party_account_currency)
}, self.party_account_currency, item=self)
)
def make_item_gl_entries(self, gl_entries):
@ -841,7 +841,7 @@ class PurchaseInvoice(BuyingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
}, self.party_account_currency)
}, self.party_account_currency, item=self)
)
gl_entries.append(
@ -852,7 +852,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_paid_amount \
if bank_account_currency==self.company_currency else self.paid_amount,
"cost_center": self.cost_center
}, bank_account_currency)
}, bank_account_currency, item=self)
)
def make_write_off_gl_entry(self, gl_entries):
@ -873,7 +873,7 @@ class PurchaseInvoice(BuyingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center
}, self.party_account_currency)
}, self.party_account_currency, item=self)
)
gl_entries.append(
self.get_gl_dict({
@ -883,7 +883,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_write_off_amount \
if write_off_account_currency==self.company_currency else self.write_off_amount,
"cost_center": self.cost_center or self.write_off_cost_center
})
}, item=self)
)
def make_gle_for_rounding_adjustment(self, gl_entries):
@ -902,8 +902,7 @@ class PurchaseInvoice(BuyingController):
"debit_in_account_currency": self.rounding_adjustment,
"debit": self.base_rounding_adjustment,
"cost_center": self.cost_center or round_off_cost_center,
}
))
}, item=self))
def on_cancel(self):
super(PurchaseInvoice, self).on_cancel()
@ -1021,6 +1020,40 @@ class PurchaseInvoice(BuyingController):
# calculate totals again after applying TDS
self.calculate_taxes_and_totals()
def set_status(self, update=False, status=None, update_modified=True):
if self.is_new():
if self.get('amended_from'):
self.status = 'Draft'
return
precision = self.precision("outstanding_amount")
outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
if not status:
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate:
self.status = "Unpaid"
#Check if outstanding amount is 0 due to debit note issued against invoice
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
self.status = "Debit Note Issued"
elif self.is_return == 1:
self.status = "Return"
elif outstanding_amount<=0:
self.status = "Paid"
else:
self.status = "Submitted"
else:
self.status = "Draft"
if update:
self.db_set('status', self.status, update_modified = update_modified)
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context

View File

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

View File

@ -924,7 +924,7 @@ var get_healthcare_services_to_invoice = function(frm) {
if(patient && patient!=selected_patient){
selected_patient = patient;
var method = "erpnext.healthcare.utils.get_healthcare_services_to_invoice";
var args = {patient: patient};
var args = {patient: patient, company: frm.doc.company};
var columns = (["service", "reference_name", "reference_type"]);
get_healthcare_items(frm, true, $results, $placeholder, method, args, columns);
}
@ -1068,7 +1068,11 @@ 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.',
get_query: function(doc) {
return {
filters: { patient: dialog.get_value("patient"), docstatus: 1 }
filters: {
patient: dialog.get_value("patient"),
company: frm.doc.company,
docstatus: 1
}
};
}
},

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

View File

@ -58,7 +58,7 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
"rate": tax_rate_detail.tax_withholding_rate,
"threshold": tax_rate_detail.single_threshold,
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
"description": tax_withholding.category_name
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category
})
def get_tax_withholding_rates(tax_withholding, fiscal_year):

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
for i, days in enumerate([self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4]):
if row.age <= days:
if cint(row.age) <= cint(days):
index = i
break

View File

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

View File

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

View File

@ -69,6 +69,10 @@ def get_data(filters):
gl_entries_by_account = {}
opening_balances = get_opening_balances(filters)
#add filter inside list so that the query in financial_statements.py doesn't break
filters.project = [filters.project]
set_gl_entries_by_account(filters.company, filters.from_date,
filters.to_date, min_lft, max_rgt, filters, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry))
@ -102,6 +106,9 @@ def get_rootwise_opening_balances(filters, report_type):
additional_conditions += """ and cost_center in (select name from `tabCost Center`
where lft >= %s and rgt <= %s)""" % (lft, rgt)
if filters.project:
additional_conditions += " and project = %(project)s"
if filters.finance_book:
fb_conditions = " AND finance_book = %(finance_book)s"
if filters.include_default_book_entries:
@ -116,6 +123,7 @@ def get_rootwise_opening_balances(filters, report_type):
"from_date": filters.from_date,
"report_type": report_type,
"year_start_date": filters.year_start_date,
"project": filters.project,
"finance_book": filters.finance_book,
"company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book')
}

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

View File

@ -18,6 +18,10 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
refresh: function() {
var me = this;
this._super();
if (this.frm.doc.__islocal && !this.frm.doc.valid_till) {
this.frm.set_value('valid_till', frappe.datetime.add_months(this.frm.doc.transaction_date, 1));
}
if (this.frm.doc.docstatus === 1) {
cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order,
__('Create'));

View File

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

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt, nowdate, add_days
from frappe.utils import flt, nowdate, add_days, getdate
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.buying_controller import BuyingController
@ -28,6 +28,7 @@ class SupplierQuotation(BuyingController):
validate_for_items(self)
self.validate_with_previous_doc()
self.validate_uom_is_integer("uom", "qty")
self.validate_valid_till()
def on_submit(self):
frappe.db.set(self, "status", "Submitted")
@ -52,6 +53,11 @@ class SupplierQuotation(BuyingController):
"is_child_table": True
}
})
def validate_valid_till(self):
if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
frappe.throw(_("Valid till Date cannot be before Transaction Date"))
def update_rfq_supplier_status(self, include_me):
rfq_list = set([])
for item in self.items:
@ -158,3 +164,11 @@ def make_quotation(source_name, target_doc=None):
}, target_doc)
return doclist
def set_expired_status():
frappe.db.sql("""
UPDATE
`tabSupplier Quotation` SET `status` = 'Expired'
WHERE
`status` not in ('Cancelled', 'Stopped') AND `valid_till` < %s
""", (nowdate()))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ class LinkedInSettings(Document):
params = urlencode({
"response_type":"code",
"client_id": self.consumer_key,
"redirect_uri": get_site_url(frappe.local.site) + "/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?",
"redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(frappe.utils.get_url()),
"scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social"
})
@ -30,7 +30,7 @@ class LinkedInSettings(Document):
"code": code,
"client_id": self.consumer_key,
"client_secret": self.get_password(fieldname="consumer_secret"),
"redirect_uri": get_site_url(frappe.local.site) + "/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?",
"redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(frappe.utils.get_url()),
}
headers = {
"Content-Type": "application/x-www-form-urlencoded"

View File

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

View File

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

View File

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

View File

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

View File

@ -43,7 +43,8 @@ frappe.ui.form.on('Clinical Procedure', {
return {
filters: {
'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]);
}
}
frm.set_value('patient_name', data.message.patient_name);
frm.set_value('patient_age', age);
frm.set_value('patient_sex', data.message.sex);
}
});
} else {
frm.set_value('patient_name', '');
frm.set_value('patient_age', '');
frm.set_value('patient_sex', '');
}
@ -177,15 +180,35 @@ frappe.ui.form.on('Clinical Procedure', {
name: frm.doc.appointment
},
callback: function(data) {
frm.set_value('patient', data.message.patient);
frm.set_value('procedure_template', data.message.procedure_template);
frm.set_value('medical_department', data.message.department);
frm.set_value('start_date', data.message.appointment_date);
frm.set_value('start_time', data.message.appointment_time);
frm.set_value('notes', data.message.notes);
frm.set_value('service_unit', data.message.service_unit);
let values = {
'patient':data.message.patient,
'procedure_template': data.message.procedure_template,
'medical_department': data.message.department,
'practitioner': data.message.practitioner,
'start_date': data.message.appointment_date,
'start_time': data.message.appointment_time,
'notes': data.message.notes,
'service_unit': data.message.service_unit,
'company': data.message.company
};
frm.set_value(values);
}
});
} else {
let values = {
'patient': '',
'patient_name': '',
'patient_sex': '',
'patient_age': '',
'medical_department': '',
'procedure_template': '',
'start_date': '',
'start_time': '',
'notes': '',
'service_unit': '',
'inpatient_record': ''
};
frm.set_value(values);
}
},
@ -234,9 +257,11 @@ frappe.ui.form.on('Clinical Procedure', {
name: frm.doc.practitioner
},
callback: function (data) {
frappe.model.set_value(frm.doctype,frm.docname, 'medical_department',data.message.department);
frappe.model.set_value(frm.doctype,frm.docname, 'practitioner_name', data.message.practitioner_name);
}
});
} else {
frappe.model.set_value(frm.doctype,frm.docname, 'practitioner_name', '');
}
},
@ -284,14 +309,6 @@ 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', {
qty: function(frm, cdt, cdn) {
let d = locals[cdt][cdn];

View File

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

View File

@ -16,6 +16,7 @@ from frappe.model.mapper import get_mapped_doc
class ClinicalProcedure(Document):
def validate(self):
self.set_status()
self.set_title()
if self.consume_stock:
self.set_actual_qty()
@ -37,7 +38,7 @@ class ClinicalProcedure(Document):
template = frappe.get_doc('Clinical Procedure Template', self.procedure_template)
if template.sample:
patient = frappe.get_doc('Patient', self.patient)
sample_collection = create_sample_doc(template, patient, None)
sample_collection = create_sample_doc(template, patient, None, self.company)
frappe.db.set_value('Clinical Procedure', self.name, 'sample', sample_collection.name)
self.reload()
@ -50,6 +51,9 @@ class ClinicalProcedure(Document):
elif self.docstatus == 2:
self.status = 'Cancelled'
def set_title(self):
self.title = _('{0} - {1}').format(self.patient_name or self.patient, self.procedure_template)[:100]
def complete_procedure(self):
if self.consume_stock and self.items:
stock_entry = make_stock_entry(self)

View File

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

View File

@ -240,7 +240,7 @@
"label": "Patient Registration"
},
{
"default": "Hello {{doc.patient}}, Thank you for registering with {{doc.company}}. Your ID is {{doc.id}} . Please note this ID for future reference. \nThank You, Get well soon!",
"default": "Hello {{doc.patient}}, Thank you for registering with {{doc.company}}. Your ID is {{doc.name}} . Please note this ID for future reference. \nThank You!",
"depends_on": "send_registration_msg",
"fieldname": "registration_msg",
"fieldtype": "Small Text",
@ -254,7 +254,7 @@
"label": "Appointment Confirmation"
},
{
"default": "Hello {{doc.patient}}, You have scheduled an appointment with {{doc.practitioner}} by {{doc.start_dt}} at {{doc.company}}.\nThank you, Good day!",
"default": "Hello {{doc.patient}}, You have scheduled an appointment with {{doc.practitioner}} on {{doc.appointment_datetime}} at {{doc.company}}.\nThank you, Good day!",
"depends_on": "send_appointment_confirmation",
"fieldname": "appointment_confirmation_msg",
"fieldtype": "Small Text",
@ -276,7 +276,7 @@
"label": "Appointment Reminder"
},
{
"default": "Hello {{doc.patient}}, You have an appointment with {{doc.practitioner}} by {{doc.appointment_time}} at {{doc.company}}.\nThank you, Good day!\n",
"default": "Hello {{doc.patient}}, You have an appointment with {{doc.practitioner}} by {{doc.appointment_datetime}} at {{doc.company}}.\nThank you, Good day!\n",
"depends_on": "send_appointment_reminder",
"fieldname": "appointment_reminder_msg",
"fieldtype": "Small Text",

View File

@ -8,7 +8,6 @@ import unittest
from frappe.utils import now_datetime, today
from frappe.utils.make_random import get_random
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient
class TestInpatientRecord(unittest.TestCase):
def test_admit_and_discharge(self):
@ -112,3 +111,13 @@ def get_service_unit_type():
service_unit_type.save(ignore_permissions = True)
return service_unit_type.name
return service_unit_type
def create_patient():
patient = frappe.db.exists('Patient', '_Test IPD Patient')
if not patient:
patient = frappe.new_doc('Patient')
patient.first_name = '_Test IPD Patient'
patient.sex = 'Female'
patient.save(ignore_permissions=True)
patient = patient.name
return patient

View File

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

View File

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

View File

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

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

View File

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

View File

@ -10,6 +10,7 @@ from frappe.utils import cint, cstr, getdate
import dateutil
from frappe.model.naming import set_name_by_naming_series
from frappe.utils.nestedset import get_root_of
from erpnext import get_default_currency
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account, send_registration_sms
class Patient(Document):
@ -17,6 +18,9 @@ class Patient(Document):
self.set_full_name()
self.add_as_website_user()
def before_insert(self):
self.set_missing_customer_details()
def after_insert(self):
self.add_as_website_user()
self.reload()
@ -26,6 +30,25 @@ class Patient(Document):
frappe.db.set_value('Patient', self.name, 'status', 'Disabled')
else:
send_registration_sms(self)
self.reload() # self.notify_update()
def on_update(self):
if self.customer:
customer = frappe.get_doc('Customer', self.customer)
if self.customer_group:
customer.customer_group = self.customer_group
if self.territory:
customer.territory = self.territory
customer.customer_name = self.patient_name
customer.default_price_list = self.default_price_list
customer.default_currency = self.default_currency
customer.language = self.language
customer.ignore_mandatory = True
customer.save(ignore_permissions=True)
else:
if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'):
create_customer(self)
def set_full_name(self):
if self.last_name:
@ -33,6 +56,22 @@ class Patient(Document):
else:
self.patient_name = self.first_name
def set_missing_customer_details(self):
if not self.customer_group:
self.customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group') or get_root_of('Customer Group')
if not self.territory:
self.territory = frappe.db.get_single_value('Selling Settings', 'territory') or get_root_of('Territory')
if not self.default_price_list:
self.default_price_list = frappe.db.get_single_value('Selling Settings', 'selling_price_list')
if not self.customer_group or not self.territory or not self.default_price_list:
frappe.msgprint(_('Please set defaults for Customer Group, Territory and Selling Price List in Selling Settings'), alert=True)
if not self.default_currency:
self.default_currency = get_default_currency()
if not self.language:
self.language = frappe.db.get_single_value('System Settings', 'language')
def add_as_website_user(self):
if self.email:
if not frappe.db.exists ('User', self.email):
@ -86,19 +125,15 @@ class Patient(Document):
return {'invoice': sales_invoice.name}
def create_customer(doc):
customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group')
territory = frappe.db.get_single_value('Selling Settings', 'territory')
if not (customer_group and territory):
customer_group = get_root_of('Customer Group')
territory = get_root_of('Territory')
frappe.msgprint(_('Please set default customer group and territory in Selling Settings'), alert=True)
customer = frappe.get_doc({
'doctype': 'Customer',
'customer_name': doc.patient_name,
'customer_group': customer_group,
'territory' : territory,
'customer_type': 'Individual'
'customer_group': doc.customer_group or frappe.db.get_single_value('Selling Settings', 'customer_group'),
'territory' : doc.territory or frappe.db.get_single_value('Selling Settings', 'territory'),
'customer_type': 'Individual',
'default_currency': doc.default_currency,
'default_price_list': doc.default_price_list,
'language': doc.language
}).insert(ignore_permissions=True, ignore_mandatory=True)
frappe.db.set_value('Patient', doc.name, 'customer', customer.name)

View File

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

View File

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

View File

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

View File

@ -4,14 +4,15 @@
from __future__ import unicode_literals
import unittest
import frappe
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
from frappe.utils import nowdate, add_days
from frappe.utils.make_random import get_random
class TestPatientAppointment(unittest.TestCase):
def setUp(self):
frappe.db.sql("""delete from `tabPatient Appointment`""")
frappe.db.sql("""delete from `tabFee Validity""")
frappe.db.sql("""delete from `tabFee Validity`""")
frappe.db.sql("""delete from `tabPatient Encounter`""")
def test_status(self):
patient, medical_department, practitioner = create_healthcare_docs()
@ -23,6 +24,19 @@ class TestPatientAppointment(unittest.TestCase):
create_encounter(appointment)
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
def test_start_encounter(self):
patient, medical_department, practitioner = create_healthcare_docs()
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1)
self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1)
encounter = make_encounter(appointment.name)
self.assertTrue(encounter)
self.assertEqual(encounter.company, appointment.company)
self.assertEqual(encounter.practitioner, appointment.practitioner)
self.assertEqual(encounter.patient, appointment.patient)
# invoiced flag mapped from appointment
self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'))
def test_invoicing(self):
patient, medical_department, practitioner = create_healthcare_docs()
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
@ -33,7 +47,11 @@ class TestPatientAppointment(unittest.TestCase):
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), invoice=1)
self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1)
self.assertTrue(frappe.db.get_value('Patient Appointment', appointment.name, 'ref_sales_invoice'))
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
self.assertTrue(sales_invoice_name)
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'company'), appointment.company)
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'patient'), appointment.patient)
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
def test_appointment_cancel(self):
patient, medical_department, practitioner = create_healthcare_docs()
@ -53,8 +71,8 @@ class TestPatientAppointment(unittest.TestCase):
appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
update_status(appointment.name, 'Cancelled')
# check invoice cancelled
sales_invoice = frappe.db.get_value('Patient Appointment', appointment.name, 'ref_sales_invoice')
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice, 'status'), 'Cancelled')
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled')
def create_healthcare_docs():
@ -90,14 +108,15 @@ def create_patient():
patient = patient.name
return patient
def create_encounter(appointment=None):
encounter = frappe.new_doc('Patient Encounter')
def create_encounter(appointment):
if appointment:
encounter = frappe.new_doc('Patient Encounter')
encounter.appointment = appointment.name
encounter.patient = appointment.patient
encounter.practitioner = appointment.practitioner
encounter.encounter_date = appointment.appointment_date
encounter.encounter_time = appointment.appointment_time
encounter.company = appointment.company
encounter.save()
encounter.submit()
return encounter

View File

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

View File

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

View File

@ -10,6 +10,9 @@ from frappe.utils import cstr
from frappe import _
class PatientEncounter(Document):
def validate(self):
self.set_title()
def on_update(self):
if self.appointment:
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
@ -29,6 +32,10 @@ class PatientEncounter(Document):
def on_submit(self):
create_therapy_plan(self)
def set_title(self):
self.title = _('{0} with {1}').format(self.patient_name or self.patient,
self.practitioner_name or self.practitioner)[:100]
def create_therapy_plan(encounter):
if len(encounter.therapies):
doc = frappe.new_doc('Therapy Plan')

View File

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

View File

@ -9,6 +9,16 @@ frappe.ui.form.on('Therapy Session', {
{fieldname: 'counts_completed', columns: 1},
{fieldname: 'assistance_level', columns: 1}
];
frm.set_query('service_unit', function() {
return {
filters: {
'is_group': false,
'allow_appointments': true,
'company': frm.doc.company
}
};
});
},
refresh: function(frm) {

View File

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

View File

@ -9,12 +9,19 @@ from frappe.utils import cstr
from frappe import _
class VitalSigns(Document):
def validate(self):
self.set_title()
def on_submit(self):
insert_vital_signs_to_medical_record(self)
def on_cancel(self):
delete_vital_signs_from_medical_record(self)
def set_title(self):
self.title = _('{0} on {1}').format(self.patient_name or self.patient,
frappe.utils.format_date(self.signs_date))[:100]
def insert_vital_signs_to_medical_record(doc):
subject = set_subject_field(doc)
medical_record = frappe.new_doc('Patient Medical Record')

View File

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

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.set_email_campaign_status",
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status"
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status"
],
"daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send",

View File

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

View File

@ -14,6 +14,8 @@
"is_group",
"disabled",
"section_break_4",
"payroll_cost_center",
"column_break_9",
"leave_block_list",
"leave_section",
"leave_approvers",
@ -125,13 +127,23 @@
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "payroll_cost_center",
"fieldtype": "Link",
"label": "Payroll Cost Center",
"options": "Cost Center"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-sitemap",
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2020-03-18 18:03:27.784362",
"modified": "2020-05-05 18:49:28.503931",
"modified_by": "Administrator",
"module": "HR",
"name": "Department",

View File

@ -60,6 +60,8 @@
"default_shift",
"salary_information",
"salary_mode",
"payroll_cost_center",
"column_break_52",
"bank_name",
"bank_ac_no",
"health_insurance_section",
@ -783,13 +785,25 @@
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
},
{
"fetch_from": "department.payroll_cost_center",
"fetch_if_empty": 1,
"fieldname": "payroll_cost_center",
"fieldtype": "Link",
"label": "Payroll Cost Center",
"options": "Cost Center"
},
{
"fieldname": "column_break_52",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-user",
"idx": 24,
"image_field": "image",
"links": [],
"modified": "2020-04-08 12:25:34.306695",
"modified": "2020-05-05 18:51:03.152503",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -88,32 +88,40 @@ def get_previous_expiry_ledger_entry(ledger):
}, fieldname=['name'])
def process_expired_allocation():
''' Check if a carry forwarded allocation has expired and create a expiry ledger entry '''
''' Check if a carry forwarded allocation has expired and create a expiry ledger entry
Case 1: carry forwarded expiry period is set for the leave type,
create a separate leave expiry entry against each entry of carry forwarded and non carry forwarded leaves
Case 2: leave type has no specific expiry period for carry forwarded leaves
and there is no carry forwarded leave allocation, create a single expiry against the remaining leaves.
'''
# fetch leave type records that has carry forwarded leaves expiry
leave_type_records = frappe.db.get_values("Leave Type", filters={
'expire_carry_forwarded_leaves_after_days': (">", 0)
}, fieldname=['name'])
leave_type = [record[0] for record in leave_type_records]
leave_type = [record[0] for record in leave_type_records] or ['']
expired_allocation = frappe.db.sql_list("""SELECT name
FROM `tabLeave Ledger Entry`
WHERE
`transaction_type`='Leave Allocation'
AND `is_expired`=1""")
expire_allocation = frappe.get_all("Leave Ledger Entry",
fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
filters={
'to_date': ("<", today()),
'transaction_type': 'Leave Allocation',
'transaction_name': ('not in', expired_allocation)
},
or_filters={
'is_carry_forward': 0,
'leave_type': ('in', leave_type)
})
# fetch non expired leave ledger entry of transaction_type allocation
expire_allocation = frappe.db.sql("""
SELECT
leaves, to_date, employee, leave_type,
is_carry_forward, transaction_name as name, transaction_type
FROM `tabLeave Ledger Entry` l
WHERE (NOT EXISTS
(SELECT name
FROM `tabLeave Ledger Entry`
WHERE
transaction_name = l.transaction_name
AND transaction_type = 'Leave Allocation'
AND name<>l.name
AND docstatus = 1
AND (
is_carry_forward=l.is_carry_forward
OR (is_carry_forward = 0 AND leave_type not in %s)
)))
AND transaction_type = 'Leave Allocation'
AND to_date < %s""", (leave_type, today()), as_dict=1)
if expire_allocation:
create_expiry_ledger_entry(expire_allocation)
@ -133,6 +141,7 @@ def get_remaining_leaves(allocation):
'employee': allocation.employee,
'leave_type': allocation.leave_type,
'to_date': ('<=', allocation.to_date),
'docstatus': 1
}, fieldname=['SUM(leaves)'])
@frappe.whitelist()
@ -159,7 +168,8 @@ def expire_allocation(allocation, expiry_date=None):
def expire_carried_forward_allocation(allocation):
''' Expires remaining leaves in the on carried forward allocation '''
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date)
leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type,
allocation.from_date, allocation.to_date, do_not_skip_expired_leaves=True)
leaves = flt(allocation.leaves) + flt(leaves_taken)
# allow expired leaves entry to be created

View File

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

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.employee.test_employee import make_employee
from erpnext.hr.doctype.salary_slip.test_salary_slip import get_salary_component_account, \
make_earning_salary_component, make_deduction_salary_component
make_earning_salary_component, make_deduction_salary_component, create_account
from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure
from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
class TestPayrollEntry(unittest.TestCase):
def setUp(self):
for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry", "Salary Structure"]:
frappe.db.sql("delete from `tab%s`" % dt)
for dt in ["Salary Slip", "Salary Component", "Salary Component Account",
"Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]:
frappe.db.sql("delete from `tab%s`" % dt)
make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_salary_component(setup=True, company_list=["_Test Company"])
@ -33,11 +34,59 @@ class TestPayrollEntry(unittest.TestCase):
get_salary_component_account(data.name)
employee = frappe.db.get_value("Employee", {'company': company})
make_salary_structure("_Test Salary Structure", "Monthly", employee)
make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company)
dates = get_start_end_dates('Monthly', nowdate())
if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date)
def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use
for data in frappe.get_all('Salary Component', fields = ["name"]):
if not frappe.db.get_value('Salary Component Account',
{'parent': data.name, 'company': "_Test Company"}, 'name'):
get_salary_component_account(data.name)
if not frappe.db.exists('Department', "cc - _TC"):
frappe.get_doc({
'doctype': 'Department',
'department_name': "cc",
"company": "_Test Company"
}).insert()
employee1 = make_employee("test_employee1@example.com", payroll_cost_center="_Test Cost Center - _TC",
department="cc - _TC", company="_Test Company")
employee2 = make_employee("test_employee2@example.com", payroll_cost_center="_Test Cost Center 2 - _TC",
department="cc - _TC", company="_Test Company")
make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company")
make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company")
if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"):
create_account(account_name="_Test Payroll Payable",
company="_Test Company", parent_account="Current Liabilities - _TC")
frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
"_Test Payroll Payable - _TC")
dates = get_start_end_dates('Monthly', nowdate())
if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC")
je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
je_entries = frappe.db.sql("""
select account, cost_center, debit, credit
from `tabJournal Entry Account`
where parent=%s
order by account, cost_center
""", je)
expected_je = (
('_Test Payroll Payable - _TC', 'Main - _TC', 0.0, 155600.0),
('Salary - _TC', '_Test Cost Center - _TC', 78000.0, 0.0),
('Salary - _TC', '_Test Cost Center 2 - _TC', 78000.0, 0.0),
('Salary Deductions - _TC', '_Test Cost Center - _TC', 0.0, 200.0),
('Salary Deductions - _TC', '_Test Cost Center 2 - _TC', 0.0, 200.0)
)
self.assertEqual(je_entries, expected_je)
def test_get_end_date(self):
self.assertEqual(get_end_date('2017-01-01', 'monthly'), {'end_date': '2017-01-31'})
self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'})
@ -49,7 +98,6 @@ class TestPayrollEntry(unittest.TestCase):
self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'})
def test_loan(self):
branch = "Test Employee Branch"
applicant = make_employee("test_employee@loan.com", company="_Test Company")
company = "_Test Company"
@ -116,6 +164,7 @@ def make_payroll_entry(**args):
payroll_entry.posting_date = nowdate()
payroll_entry.payroll_frequency = "Monthly"
payroll_entry.branch = args.branch or None
payroll_entry.department = args.department or None
if args.cost_center:
payroll_entry.cost_center = args.cost_center
@ -123,6 +172,7 @@ def make_payroll_entry(**args):
if args.payment_account:
payroll_entry.payment_account = args.payment_account
payroll_entry.fill_employee_details()
payroll_entry.save()
payroll_entry.create_salary_slips()
payroll_entry.submit_salary_slips()

View File

@ -12,6 +12,7 @@
"department",
"designation",
"branch",
"payroll_cost_center",
"column_break1",
"status",
"journal_entry",
@ -459,13 +460,22 @@
"options": "Salary Slip",
"print_hide": 1,
"read_only": 1
},
{
"fetch_from": "employee.payroll_cost_center",
"fetch_if_empty": 1,
"fieldname": "payroll_cost_center",
"fieldtype": "Link",
"label": "Payroll Cost Center",
"options": "Cost Center",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 9,
"is_submittable": 1,
"links": [],
"modified": "2020-04-14 20:02:53.159827",
"modified": "2020-05-05 18:55:26.173629",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Slip",

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

View File

@ -153,12 +153,16 @@ def make_salary_slip(source_name, target_doc = None, employee = None, as_print =
def postprocess(source, target):
if employee:
employee_details = frappe.db.get_value("Employee", employee,
["employee_name", "branch", "designation", "department"], as_dict=1)
["employee_name", "branch", "designation", "department", "payroll_cost_center"], as_dict=1)
target.employee = employee
target.employee_name = employee_details.employee_name
target.branch = employee_details.branch
target.designation = employee_details.designation
target.department = employee_details.department
target.payroll_cost_center = employee_details.payroll_cost_center
if not target.payroll_cost_center and target.department:
target.payroll_cost_center = frappe.db.get_value("Department", target.department, "payroll_cost_center")
target.run_method('process_salary_structure', for_preview=for_preview)
doc = get_mapped_doc("Salary Structure", source_name, {

View File

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

View File

@ -38,7 +38,7 @@ class LoanSecurityPledge(Document):
for pledge in self.securities:
if not pledge.qty and not pledge.amount:
frappe.throw(_("Qty or Amount is mandatroy for loan security"))
frappe.throw(_("Qty or Amount is mandatory for loan security!"))
if not (self.loan_application and pledge.loan_security_price):
pledge.loan_security_price = get_loan_security_price(pledge.loan_security)

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

View File

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

View File

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

View File

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

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.add_default_buying_selling_terms_in_company
erpnext.patches.v12_0.update_ewaybill_field_position
erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes
erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes #2020-05-11
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
erpnext.patches.v12_0.move_plaid_settings_to_doctype
execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_link')
@ -678,6 +678,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.update_appointment_reminder_scheduler_entry
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype
erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
execute:frappe.delete_doc_if_exists("Page", "appointment-analytic")
execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True)
erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price
erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation
erpnext.patches.v12_0.set_serial_no_status
erpnext.patches.v12_0.update_price_list_currency_in_bom
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')

View File

@ -20,7 +20,8 @@ def execute():
else:
insert_after_field = 'accounting_dimensions_section'
for doctype in ["Subscription Plan", "Subscription", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item"]:
for doctype in ["Subscription Plan", "Subscription", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item",
"Expense Claim Detail", "Expense Taxes and Charges"]:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})

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