Merge branch 'develop' of https://github.com/frappe/erpnext into mpesa-integration

This commit is contained in:
Mangesh-Khairnar 2020-09-28 02:10:53 +05:30
commit 98658603dd
172 changed files with 3635 additions and 3909 deletions

View File

@ -16,7 +16,7 @@
ERPNext as a monolith includes the following areas for managing businesses: ERPNext as a monolith includes the following areas for managing businesses:
1. [Accounting](https://erpnext.com/open-source-accounting) 1. [Accounting](https://erpnext.com/open-source-accounting)
1. [Inventory](https://erpnext.com/distribution/inventory-management-system) 1. [Warehouse Management](https://erpnext.com/distribution/warehouse-management-system)
1. [CRM](https://erpnext.com/open-source-crm) 1. [CRM](https://erpnext.com/open-source-crm)
1. [Sales](https://erpnext.com/open-source-sales-purchase) 1. [Sales](https://erpnext.com/open-source-sales-purchase)
1. [Purchase](https://erpnext.com/open-source-sales-purchase) 1. [Purchase](https://erpnext.com/open-source-sales-purchase)

View File

@ -43,7 +43,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Bank Statement", "label": "Bank Statement",
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]" "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -98,7 +98,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Accounting", "label": "Accounting",
"modified": "2020-09-03 10:37:07.865801", "modified": "2020-09-09 11:45:33.766400",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting", "name": "Accounting",
@ -147,11 +147,6 @@
"link_to": "Trial Balance", "link_to": "Trial Balance",
"type": "Report" "type": "Report"
}, },
{
"label": "Point of Sale",
"link_to": "point-of-sale",
"type": "Page"
},
{ {
"label": "Dashboard", "label": "Dashboard",
"link_to": "Accounts", "link_to": "Accounts",

View File

@ -55,7 +55,7 @@ class BankStatementTransactionEntry(Document):
def populate_payment_entries(self): def populate_payment_entries(self):
if self.bank_statement is None: return if self.bank_statement is None: return
filename = self.bank_statement.split("/")[-1] file_url = self.bank_statement
if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0): if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0):
frappe.throw(_("Transactions already retreived from the statement")) frappe.throw(_("Transactions already retreived from the statement"))
@ -65,7 +65,7 @@ class BankStatementTransactionEntry(Document):
if self.bank_settings: if self.bank_settings:
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
statement_headers = self.get_statement_headers() statement_headers = self.get_statement_headers()
transactions = get_transaction_entries(filename, statement_headers) transactions = get_transaction_entries(file_url, statement_headers)
for entry in transactions: for entry in transactions:
date = entry[statement_headers["Date"]].strip() date = entry[statement_headers["Date"]].strip()
#print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"])) #print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"]))
@ -398,20 +398,21 @@ def get_transaction_info(headers, header_index, row):
transaction[header] = "" transaction[header] = ""
return transaction return transaction
def get_transaction_entries(filename, headers): def get_transaction_entries(file_url, headers):
header_index = {} header_index = {}
rows, transactions = [], [] rows, transactions = [], []
if (filename.lower().endswith("xlsx")): if (file_url.lower().endswith("xlsx")):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(file_id=filename) rows = read_xlsx_file_from_attached_file(file_url=file_url)
elif (filename.lower().endswith("csv")): elif (file_url.lower().endswith("csv")):
from frappe.utils.csvutils import read_csv_content from frappe.utils.csvutils import read_csv_content
_file = frappe.get_doc("File", {"file_name": filename}) _file = frappe.get_doc("File", {"file_url": file_url})
filepath = _file.get_full_path() filepath = _file.get_full_path()
with open(filepath,'rb') as csvfile: with open(filepath,'rb') as csvfile:
rows = read_csv_content(csvfile.read()) rows = read_csv_content(csvfile.read())
elif (filename.lower().endswith("xls")): elif (file_url.lower().endswith("xls")):
filename = file_url.split("/")[-1]
rows = get_rows_from_xls_file(filename) rows = get_rows_from_xls_file(filename)
else: else:
frappe.throw(_("Only .csv and .xlsx files are supported currently")) frappe.throw(_("Only .csv and .xlsx files are supported currently"))

View File

@ -38,8 +38,8 @@
"reqd": 1 "reqd": 1
} }
], ],
"modified": "2020-06-18 20:27:42.615842", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "ahmad@havenir.com", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Item Tax Template", "name": "Item Tax Template",
"owner": "Administrator", "owner": "Administrator",

View File

@ -52,7 +52,7 @@
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Mode of Payment", "name": "Mode of Payment",
"owner": "harshada@webnotestech.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"create": 1, "create": 1,

View File

@ -12,9 +12,10 @@ frappe.ui.form.on('Payment Entry', {
setup: function(frm) { setup: function(frm) {
frm.set_query("paid_from", function() { frm.set_query("paid_from", function() {
frm.events.validate_company(frm);
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ? var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
return { return {
filters: { filters: {
"account_type": ["in", account_types], "account_type": ["in", account_types],
@ -23,13 +24,16 @@ frappe.ui.form.on('Payment Entry', {
} }
} }
}); });
frm.set_query("party_type", function() { frm.set_query("party_type", function() {
frm.events.validate_company(frm);
return{ return{
filters: { filters: {
"name": ["in", Object.keys(frappe.boot.party_account_types)], "name": ["in", Object.keys(frappe.boot.party_account_types)],
} }
} }
}); });
frm.set_query("party_bank_account", function() { frm.set_query("party_bank_account", function() {
return { return {
filters: { filters: {
@ -39,6 +43,7 @@ frappe.ui.form.on('Payment Entry', {
} }
} }
}); });
frm.set_query("bank_account", function() { frm.set_query("bank_account", function() {
return { return {
filters: { filters: {
@ -47,6 +52,7 @@ frappe.ui.form.on('Payment Entry', {
} }
} }
}); });
frm.set_query("contact_person", function() { frm.set_query("contact_person", function() {
if (frm.doc.party) { if (frm.doc.party) {
return { return {
@ -58,10 +64,12 @@ frappe.ui.form.on('Payment Entry', {
}; };
} }
}); });
frm.set_query("paid_to", function() { frm.set_query("paid_to", function() {
frm.events.validate_company(frm);
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ? var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]]; ["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
return { return {
filters: { filters: {
"account_type": ["in", account_types], "account_type": ["in", account_types],
@ -150,6 +158,12 @@ frappe.ui.form.on('Payment Entry', {
frm.events.show_general_ledger(frm); frm.events.show_general_ledger(frm);
}, },
validate_company: (frm) => {
if (!frm.doc.company){
frappe.throw({message:__("Please select a Company first."), title: __("Mandatory")});
}
},
company: function(frm) { company: function(frm) {
frm.events.hide_unhide_fields(frm); frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm); frm.events.set_dynamic_labels(frm);

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2016-06-01 14:38:51.012597", "creation": "2016-06-01 14:38:51.012597",
@ -63,6 +64,7 @@
"cost_center", "cost_center",
"section_break_12", "section_break_12",
"status", "status",
"custom_remarks",
"remarks", "remarks",
"column_break_16", "column_break_16",
"letter_head", "letter_head",
@ -462,7 +464,8 @@
"fieldname": "remarks", "fieldname": "remarks",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Remarks", "label": "Remarks",
"no_copy": 1 "no_copy": 1,
"read_only_depends_on": "eval:doc.custom_remarks == 0"
}, },
{ {
"fieldname": "column_break_16", "fieldname": "column_break_16",
@ -573,10 +576,18 @@
"label": "Status", "label": "Status",
"options": "\nDraft\nSubmitted\nCancelled", "options": "\nDraft\nSubmitted\nCancelled",
"read_only": 1 "read_only": 1
},
{
"default": "0",
"fieldname": "custom_remarks",
"fieldtype": "Check",
"label": "Custom Remarks"
} }
], ],
"index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-12-08 13:02:30.016610", "links": [],
"modified": "2020-09-02 13:39:43.383705",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",

View File

@ -453,7 +453,7 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction")) frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
def set_remarks(self): def set_remarks(self):
if self.remarks: return if self.custom_remarks: return
if self.payment_type=="Internal Transfer": if self.payment_type=="Internal Transfer":
remarks = [_("Amount {0} {1} transferred from {2} to {3}") remarks = [_("Amount {0} {1} transferred from {2} to {3}")

View File

@ -291,11 +291,11 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-08-21 16:15:49.089450", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Period Closing Voucher", "name": "Period Closing Voucher",
"owner": "jai@webnotestech.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1, "amend": 1,

View File

@ -55,14 +55,48 @@ frappe.ui.form.on('POS Closing Entry', {
}, },
callback: (r) => { callback: (r) => {
let pos_docs = r.message; let pos_docs = r.message;
set_form_data(pos_docs, frm) set_form_data(pos_docs, frm);
refresh_fields(frm) refresh_fields(frm);
set_html_data(frm) set_html_data(frm);
} }
}) })
} }
}); });
cur_frm.cscript.before_pos_transactions_remove = function(doc, cdt, cdn) {
const removed_row = locals[cdt][cdn];
if (!removed_row.pos_invoice) return;
frappe.db.get_doc("POS Invoice", removed_row.pos_invoice).then(doc => {
cur_frm.doc.grand_total -= flt(doc.grand_total);
cur_frm.doc.net_total -= flt(doc.net_total);
cur_frm.doc.total_quantity -= flt(doc.total_qty);
refresh_payments(doc, cur_frm, 1);
refresh_taxes(doc, cur_frm, 1);
refresh_fields(cur_frm);
set_html_data(cur_frm);
});
}
frappe.ui.form.on('POS Invoice Reference', {
pos_invoice(frm, cdt, cdn) {
const added_row = locals[cdt][cdn];
if (!added_row.pos_invoice) return;
frappe.db.get_doc("POS Invoice", added_row.pos_invoice).then(doc => {
frm.doc.grand_total += flt(doc.grand_total);
frm.doc.net_total += flt(doc.net_total);
frm.doc.total_quantity += flt(doc.total_qty);
refresh_payments(doc, frm);
refresh_taxes(doc, frm);
refresh_fields(frm);
set_html_data(frm);
});
}
})
frappe.ui.form.on('POS Closing Entry Detail', { frappe.ui.form.on('POS Closing Entry Detail', {
closing_amount: (frm, cdt, cdn) => { closing_amount: (frm, cdt, cdn) => {
const row = locals[cdt][cdn]; const row = locals[cdt][cdn];
@ -76,8 +110,8 @@ function set_form_data(data, frm) {
frm.doc.grand_total += flt(d.grand_total); frm.doc.grand_total += flt(d.grand_total);
frm.doc.net_total += flt(d.net_total); frm.doc.net_total += flt(d.net_total);
frm.doc.total_quantity += flt(d.total_qty); frm.doc.total_quantity += flt(d.total_qty);
add_to_payments(d, frm); refresh_payments(d, frm);
add_to_taxes(d, frm); refresh_taxes(d, frm);
}); });
} }
@ -90,11 +124,12 @@ function add_to_pos_transaction(d, frm) {
}) })
} }
function add_to_payments(d, frm) { function refresh_payments(d, frm, remove) {
d.payments.forEach(p => { d.payments.forEach(p => {
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment); const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
if (payment) { if (payment) {
payment.expected_amount += flt(p.amount); if (!remove) payment.expected_amount += flt(p.amount);
else payment.expected_amount -= flt(p.amount);
} else { } else {
frm.add_child("payment_reconciliation", { frm.add_child("payment_reconciliation", {
mode_of_payment: p.mode_of_payment, mode_of_payment: p.mode_of_payment,
@ -105,11 +140,12 @@ function add_to_payments(d, frm) {
}) })
} }
function add_to_taxes(d, frm) { function refresh_taxes(d, frm, remove) {
d.taxes.forEach(t => { d.taxes.forEach(t => {
const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate); const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
if (tax) { if (tax) {
tax.amount += flt(t.tax_amount); if (!remove) tax.amount += flt(t.tax_amount);
else tax.amount -= flt(t.tax_amount);
} else { } else {
frm.add_child("taxes", { frm.add_child("taxes", {
account_head: t.account_head, account_head: t.account_head,

View File

@ -279,7 +279,8 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Return (Credit Note)", "label": "Is Return (Credit Note)",
"no_copy": 1, "no_copy": 1,
"print_hide": 1 "print_hide": 1,
"set_only_once": 1
}, },
{ {
"fieldname": "column_break1", "fieldname": "column_break1",
@ -1578,9 +1579,10 @@
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-29 15:08:39.337385", "modified": "2020-09-07 12:43:09.138720",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Invoice", "name": "POS Invoice",

View File

@ -182,8 +182,9 @@ class TestPOSInvoice(unittest.TestCase):
def test_pos_returns_with_repayment(self): def test_pos_returns_with_repayment(self):
pos = create_pos_invoice(qty = 10, do_not_save=True) pos = create_pos_invoice(qty = 10, do_not_save=True)
pos.set('payments', [])
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500}) pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500}) pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500, 'default': 1})
pos.insert() pos.insert()
pos.submit() pos.submit()
@ -200,8 +201,9 @@ class TestPOSInvoice(unittest.TestCase):
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105, income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
cost_center = "Main - _TC", do_not_save=True) cost_center = "Main - _TC", do_not_save=True)
pos.set('payments', [])
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50}) pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50})
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60}) pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60, 'default': 1})
pos.insert() pos.insert()
pos.submit() pos.submit()

View File

@ -24,11 +24,20 @@ class POSInvoiceMergeLog(Document):
def validate_pos_invoice_status(self): def validate_pos_invoice_status(self):
for d in self.pos_invoices: for d in self.pos_invoices:
status, docstatus = frappe.db.get_value('POS Invoice', d.pos_invoice, ['status', 'docstatus']) status, docstatus, is_return, return_against = frappe.db.get_value(
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
if docstatus != 1: if docstatus != 1:
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice)) frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice))
if status in ['Consolidated']: if status == "Consolidated":
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status)) frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status))
if is_return and return_against not in [d.pos_invoice for d in self.pos_invoices] and status != "Consolidated":
# if return entry is not getting merged in the current pos closing and if it is not consolidated
frappe.throw(
_("Row #{}: Return Invoice {} cannot be made against unconsolidated invoice. \
You can add original invoice {} manually to proceed.")
.format(d.idx, frappe.bold(d.pos_invoice), frappe.bold(return_against))
)
def on_submit(self): def on_submit(self):
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices] pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
@ -36,12 +45,12 @@ class POSInvoiceMergeLog(Document):
returns = [d for d in pos_invoice_docs if d.get('is_return') == 1] returns = [d for d in pos_invoice_docs if d.get('is_return') == 1]
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0] sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
sales_invoice = self.process_merging_into_sales_invoice(sales) sales_invoice, credit_note = "", ""
if sales:
sales_invoice = self.process_merging_into_sales_invoice(sales)
if len(returns): if returns:
credit_note = self.process_merging_into_credit_note(returns) credit_note = self.process_merging_into_credit_note(returns)
else:
credit_note = ""
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log

View File

@ -5,13 +5,14 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import cint from frappe.utils import cint, get_link_to_form
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.controllers.status_updater import StatusUpdater from erpnext.controllers.status_updater import StatusUpdater
class POSOpeningEntry(StatusUpdater): class POSOpeningEntry(StatusUpdater):
def validate(self): def validate(self):
self.validate_pos_profile_and_cashier() self.validate_pos_profile_and_cashier()
self.validate_payment_method_account()
self.set_status() self.set_status()
def validate_pos_profile_and_cashier(self): def validate_pos_profile_and_cashier(self):
@ -20,6 +21,14 @@ class POSOpeningEntry(StatusUpdater):
if not cint(frappe.db.get_value("User", self.user, "enabled")): if not cint(frappe.db.get_value("User", self.user, "enabled")):
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user))) frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
def validate_payment_method_account(self):
for d in self.balance_details:
account = frappe.db.get_value("Mode of Payment Account",
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
if not account:
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
def on_submit(self): def on_submit(self):
self.set_status(update=True) self.set_status(update=True)

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import msgprint, _ from frappe import msgprint, _
from frappe.utils import cint, now from frappe.utils import cint, now, get_link_to_form
from six import iteritems from six import iteritems
from frappe.model.document import Document from frappe.model.document import Document
@ -13,7 +13,7 @@ class POSProfile(Document):
self.validate_default_profile() self.validate_default_profile()
self.validate_all_link_fields() self.validate_all_link_fields()
self.validate_duplicate_groups() self.validate_duplicate_groups()
self.check_default_payment() self.validate_payment_methods()
def validate_default_profile(self): def validate_default_profile(self):
for row in self.applicable_for_users: for row in self.applicable_for_users:
@ -52,14 +52,23 @@ class POSProfile(Document):
if len(customer_groups) != len(set(customer_groups)): if len(customer_groups) != len(set(customer_groups)):
frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group") frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
def check_default_payment(self): def validate_payment_methods(self):
if self.payments: if not self.payments:
default_mode_of_payment = [d.default for d in self.payments if d.default] frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
if not default_mode_of_payment:
frappe.throw(_("Set default mode of payment"))
if len(default_mode_of_payment) > 1: default_mode_of_payment = [d.default for d in self.payments if d.default]
frappe.throw(_("Multiple default mode of payment is not allowed")) if not default_mode_of_payment:
frappe.throw(_("Please select a default mode of payment"))
if len(default_mode_of_payment) > 1:
frappe.throw(_("You can only select one mode of payment as default"))
for d in self.payments:
account = frappe.db.get_value("Mode of Payment Account",
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
if not account:
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
def on_update(self): def on_update(self):
self.set_defaults() self.set_defaults()

View File

@ -28,7 +28,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
// Trigger supplier event on load if supplier is available // Trigger supplier event on load if supplier is available
// The reason for this is PI can be created from PR or PO and supplier is pre populated // The reason for this is PI can be created from PR or PO and supplier is pre populated
if (this.frm.doc.supplier) { if (this.frm.doc.supplier && this.frm.doc.__islocal) {
this.frm.trigger('supplier'); this.frm.trigger('supplier');
} }
}, },

View File

@ -405,8 +405,6 @@ class PurchaseInvoice(BuyingController):
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
def make_gl_entries(self, gl_entries=None): def make_gl_entries(self, gl_entries=None):
if not self.grand_total:
return
if not gl_entries: if not gl_entries:
gl_entries = self.get_gl_entries() gl_entries = self.get_gl_entries()

View File

@ -210,7 +210,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-03-12 14:53:47.679439", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Taxes and Charges", "name": "Purchase Taxes and Charges",

View File

@ -78,7 +78,7 @@
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Taxes and Charges Template", "name": "Purchase Taxes and Charges Template",
"owner": "wasim@webnotestech.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"email": 1, "email": 1,

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import frappe.defaults import frappe.defaults
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form
from frappe import _, msgprint, throw from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.party import get_party_account, get_due_date
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
@ -1372,7 +1372,7 @@ def get_bank_cash_account(mode_of_payment, company):
{"parent": mode_of_payment, "company": company}, "default_account") {"parent": mode_of_payment, "company": company}, "default_account")
if not account: if not account:
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}") frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
.format(mode_of_payment)) .format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
return { return {
"account": account "account": account
} }
@ -1612,18 +1612,16 @@ def update_multi_mode_option(doc, pos_profile):
payment.type = payment_mode.type payment.type = payment_mode.type
doc.set('payments', []) doc.set('payments', [])
if not pos_profile or not pos_profile.get('payments'):
for payment_mode in get_all_mode_of_payments(doc):
append_payment(payment_mode)
return
for pos_payment_method in pos_profile.get('payments'): for pos_payment_method in pos_profile.get('payments'):
pos_payment_method = pos_payment_method.as_dict() pos_payment_method = pos_payment_method.as_dict()
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company) payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
if payment_mode: if not payment_mode:
payment_mode[0].default = pos_payment_method.default frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
append_payment(payment_mode[0]) .format(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment)), title=_("Missing Account"))
payment_mode[0].default = pos_payment_method.default
append_payment(payment_mode[0])
def get_all_mode_of_payments(doc): def get_all_mode_of_payments(doc):
return frappe.db.sql(""" return frappe.db.sql("""

View File

@ -320,7 +320,6 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
entry['remarks'] = "On cancellation of " + entry['voucher_no'] entry['remarks'] = "On cancellation of " + entry['voucher_no']
entry['is_cancelled'] = 1 entry['is_cancelled'] = 1
entry['posting_date'] = today()
if entry['debit'] or entry['credit']: if entry['debit'] or entry['credit']:
make_entry(entry, adv_adj, "Yes") make_entry(entry, adv_adj, "Yes")

View File

@ -72,7 +72,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
check_plaid_status() { check_plaid_status() {
const me = this; const me = this;
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => { frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
if (r && r.enabled == "1") { if (r && r.enabled === "1") {
me.plaid_status = "active" me.plaid_status = "active"
} else { } else {
me.plaid_status = "inactive" me.plaid_status = "inactive"
@ -139,7 +139,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
} }
make() { make() {
const me = this; const me = this;
new frappe.ui.FileUploader({ new frappe.ui.FileUploader({
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement', method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
allow_multiple: 0, allow_multiple: 0,
@ -214,31 +214,35 @@ erpnext.accounts.bankTransactionSync = class bankTransactionSync {
init_config() { init_config() {
const me = this; const me = this;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration') frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration')
.then(result => { .then(result => {
me.plaid_env = result.plaid_env; me.plaid_env = result.plaid_env;
me.plaid_public_key = result.plaid_public_key; me.client_name = result.client_name;
me.client_name = result.client_name; me.link_token = result.link_token;
me.sync_transactions() me.sync_transactions();
}) })
} }
sync_transactions() { sync_transactions() {
const me = this; const me = this;
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => { frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (r) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', { frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
bank: v['bank'], bank: r.bank,
bank_account: me.parent.bank_account, bank_account: me.parent.bank_account,
freeze: true freeze: true
}) })
.then((result) => { .then((result) => {
let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized") let result_title = (result && result.length > 0)
? __("{0} bank transaction(s) created", [result.length])
: __("This bank account is already synchronized");
let result_msg = ` let result_msg = `
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;"> <div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5> <h5 class="text-muted">${result_title}</h5>
</div>` </div>`
this.parent.$main_section.append(result_msg) this.parent.$main_section.append(result_msg)
frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'}); frappe.show_alert({ message: __("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator: 'green' });
}) })
}) })
} }
@ -384,7 +388,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
}) })
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments', frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
{bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")} { bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") }
).then((result) => { ).then((result) => {
me.make_dialog(result) me.make_dialog(result)
}) })

View File

@ -34,7 +34,7 @@ frappe.query_reports["Accounts Receivable"] = {
filters: { filters: {
'company': company 'company': company
} }
} };
} }
}, },
{ {

View File

@ -617,9 +617,19 @@ class ReceivablePayableReport(object):
elif party_type_field=="supplier": elif party_type_field=="supplier":
self.add_supplier_filters(conditions, values) self.add_supplier_filters(conditions, values)
if self.filters.cost_center:
self.get_cost_center_conditions(conditions)
self.add_accounting_dimensions_filters(conditions, values) self.add_accounting_dimensions_filters(conditions, values)
return " and ".join(conditions), values return " and ".join(conditions), values
def get_cost_center_conditions(self, conditions):
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
cost_center_list = [center.name for center in frappe.get_list("Cost Center", filters = {'lft': (">=", lft), 'rgt': ("<=", rgt)})]
cost_center_string = '", "'.join(cost_center_list)
conditions.append('cost_center in ("{0}")'.format(cost_center_string))
def get_order_by_condition(self): def get_order_by_condition(self):
if self.filters.get('group_by_party'): if self.filters.get('group_by_party'):
return "order by party, posting_date" return "order by party, posting_date"

View File

@ -268,9 +268,9 @@ class GrossProfitGenerator(object):
def get_last_purchase_rate(self, item_code, row): def get_last_purchase_rate(self, item_code, row):
condition = '' condition = ''
if row.project: if row.project:
condition += " AND a.project='%s'" % (row.project) condition += " AND a.project=%s" % (frappe.db.escape(row.project))
elif row.cost_center: elif row.cost_center:
condition += " AND a.cost_center='%s'" % (row.cost_center) condition += " AND a.cost_center=%s" % (frappe.db.escape(row.cost_center))
if self.filters.to_date: if self.filters.to_date:
condition += " AND modified='%s'" % (self.filters.to_date) condition += " AND modified='%s'" % (self.filters.to_date)

View File

@ -252,13 +252,6 @@ frappe.ui.form.on('Asset', {
}) })
}, },
available_for_use_date: function(frm) {
$.each(frm.doc.finance_books || [], function(i, d) {
if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date;
});
refresh_field("finance_books");
},
is_existing_asset: function(frm) { is_existing_asset: function(frm) {
frm.trigger("toggle_reference_doc"); frm.trigger("toggle_reference_doc");
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); // frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
@ -438,6 +431,15 @@ frappe.ui.form.on('Asset Finance Book', {
} }
frappe.flags.dont_change_rate = false; frappe.flags.dont_change_rate = false;
},
depreciation_start_date: function(frm, cdt, cdn) {
const book = locals[cdt][cdn];
if (frm.doc.available_for_use_date && book.depreciation_start_date == frm.doc.available_for_use_date) {
frappe.msgprint(__(`Depreciation Posting Date should not be equal to Available for Use Date.`));
book.depreciation_start_date = "";
frm.refresh_field("finance_books");
}
} }
}); });

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext, math, json import frappe, erpnext, math, json
from frappe import _ from frappe import _
from six import string_types from six import string_types
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days, get_last_day, get_datetime
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \ from erpnext.assets.doctype.asset.depreciation \
@ -83,6 +83,11 @@ class Asset(AccountsController):
if not self.available_for_use_date: if not self.available_for_use_date:
frappe.throw(_("Available for use date is required")) frappe.throw(_("Available for use date is required"))
for d in self.finance_books:
if d.depreciation_start_date == self.available_for_use_date:
frappe.throw(_("Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.").format(d.idx),
title=_("Incorrect Date"))
def set_missing_values(self): def set_missing_values(self):
if not self.asset_category: if not self.asset_category:
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category") self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
@ -135,6 +140,10 @@ class Asset(AccountsController):
def make_asset_movement(self): def make_asset_movement(self):
reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice' reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
reference_docname = self.purchase_receipt or self.purchase_invoice reference_docname = self.purchase_receipt or self.purchase_invoice
transaction_date = getdate(self.purchase_date)
if reference_docname:
posting_date, posting_time = frappe.db.get_value(reference_doctype, reference_docname, ["posting_date", "posting_time"])
transaction_date = get_datetime("{} {}".format(posting_date, posting_time))
assets = [{ assets = [{
'asset': self.name, 'asset': self.name,
'asset_name': self.asset_name, 'asset_name': self.asset_name,
@ -146,7 +155,7 @@ class Asset(AccountsController):
'assets': assets, 'assets': assets,
'purpose': 'Receipt', 'purpose': 'Receipt',
'company': self.company, 'company': self.company,
'transaction_date': getdate(self.purchase_date), 'transaction_date': transaction_date,
'reference_doctype': reference_doctype, 'reference_doctype': reference_doctype,
'reference_name': reference_docname 'reference_name': reference_docname
}).insert() }).insert()
@ -294,7 +303,7 @@ class Asset(AccountsController):
if not row.depreciation_start_date: if not row.depreciation_start_date:
if not self.available_for_use_date: if not self.available_for_use_date:
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx)) frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
row.depreciation_start_date = self.available_for_use_date row.depreciation_start_date = get_last_day(self.available_for_use_date)
if not self.is_existing_asset: if not self.is_existing_asset:
self.opening_accumulated_depreciation = 0 self.opening_accumulated_depreciation = 0

View File

@ -371,19 +371,18 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name) asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.available_for_use_date = nowdate() asset.available_for_use_date = '2020-01-01'
asset.purchase_date = nowdate() asset.purchase_date = '2020-01-01'
asset.append("finance_books", { asset.append("finance_books", {
"expected_value_after_useful_life": 10000, "expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 10,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 1
"depreciation_start_date": nowdate()
}) })
asset.insert() asset.insert()
asset.submit() asset.submit()
post_depreciation_entries(date=add_months(nowdate(), 10)) post_depreciation_entries(date=add_months('2020-01-01', 4))
scrap_asset(asset.name) scrap_asset(asset.name)
@ -392,9 +391,9 @@ class TestAsset(unittest.TestCase):
self.assertTrue(asset.journal_entry_for_scrap) self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = ( expected_gle = (
("_Test Accumulated Depreciations - _TC", 30000.0, 0.0), ("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0) ("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0)
) )
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
@ -466,8 +465,7 @@ class TestAsset(unittest.TestCase):
"expected_value_after_useful_life": 10000, "expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 10
"depreciation_start_date": "2020-06-06"
}) })
asset.insert() asset.insert()
accumulated_depreciation_after_full_schedule = \ accumulated_depreciation_after_full_schedule = \

View File

@ -1,347 +1,99 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2018-05-08 14:44:37.095570",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2018-05-08 14:44:37.095570", "finance_book",
"custom": 0, "depreciation_method",
"docstatus": 0, "total_number_of_depreciations",
"doctype": "DocType", "column_break_5",
"document_type": "", "frequency_of_depreciation",
"editable_grid": 1, "depreciation_start_date",
"engine": "InnoDB", "expected_value_after_useful_life",
"value_after_depreciation",
"rate_of_depreciation"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "finance_book",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Finance Book",
"collapsible": 0, "options": "Finance Book"
"columns": 0, },
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "finance_book",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Finance Book",
"length": 0,
"no_copy": 0,
"options": "Finance Book",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "depreciation_method",
"allow_in_quick_entry": 0, "fieldtype": "Select",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Depreciation Method",
"collapsible": 0, "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
"columns": 0, "reqd": 1
"fetch_if_empty": 0, },
"fieldname": "depreciation_method",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Depreciation Method",
"length": 0,
"no_copy": 0,
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "total_number_of_depreciations",
"allow_in_quick_entry": 0, "fieldtype": "Int",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Total Number of Depreciations",
"collapsible": 0, "reqd": 1
"columns": 0, },
"fetch_if_empty": 0,
"fieldname": "total_number_of_depreciations",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Total Number of Depreciations",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_5",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "frequency_of_depreciation",
"allow_in_quick_entry": 0, "fieldtype": "Int",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Frequency of Depreciation (Months)",
"collapsible": 0, "reqd": 1
"columns": 0, },
"fetch_if_empty": 0,
"fieldname": "frequency_of_depreciation",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Frequency of Depreciation (Months)",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:parent.doctype == 'Asset'",
"allow_in_quick_entry": 0, "fieldname": "depreciation_start_date",
"allow_on_submit": 0, "fieldtype": "Date",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Depreciation Posting Date",
"columns": 0, "reqd": 1
"depends_on": "eval:parent.doctype == 'Asset'", },
"fetch_if_empty": 0,
"fieldname": "depreciation_start_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Depreciation Start Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "depends_on": "eval:parent.doctype == 'Asset'",
"allow_on_submit": 0, "fieldname": "expected_value_after_useful_life",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "label": "Expected Value After Useful Life",
"columns": 0, "options": "Company:company:default_currency"
"default": "0", },
"depends_on": "eval:parent.doctype == 'Asset'",
"fetch_if_empty": 0,
"fieldname": "expected_value_after_useful_life",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Expected Value After Useful Life",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "value_after_depreciation",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "hidden": 1,
"bold": 0, "label": "Value After Depreciation",
"collapsible": 0, "no_copy": 1,
"columns": 0, "options": "Company:company:default_currency",
"fetch_if_empty": 0, "print_hide": 1,
"fieldname": "value_after_depreciation", "read_only": 1
"fieldtype": "Currency", },
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Value After Depreciation",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
"allow_in_quick_entry": 0, "description": "In Percentage",
"allow_on_submit": 0, "fieldname": "rate_of_depreciation",
"bold": 0, "fieldtype": "Percent",
"collapsible": 0, "label": "Rate of Depreciation"
"columns": 0,
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
"description": "In Percentage",
"fetch_if_empty": 0,
"fieldname": "rate_of_depreciation",
"fieldtype": "Percent",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Rate of Depreciation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "index_web_pages_for_search": 1,
"hide_toolbar": 0, "istable": 1,
"idx": 0, "links": [],
"in_create": 0, "modified": "2020-09-16 12:11:30.631788",
"is_submittable": 0, "modified_by": "Administrator",
"issingle": 0, "module": "Assets",
"istable": 1, "name": "Asset Finance Book",
"max_attachments": 0, "owner": "Administrator",
"modified": "2019-04-09 19:45:14.523488", "permissions": [],
"modified_by": "Administrator", "quick_entry": 1,
"module": "Assets", "sort_field": "modified",
"name": "Asset Finance Book", "sort_order": "DESC",
"name_case": "", "track_changes": 1
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
} }

View File

@ -32,8 +32,7 @@ class TestAssetMovement(unittest.TestCase):
"next_depreciation_date": "2020-12-31", "next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 10
"depreciation_start_date": "2020-06-06"
}) })
if asset.docstatus == 0: if asset.docstatus == 0:
@ -82,8 +81,7 @@ class TestAssetMovement(unittest.TestCase):
"next_depreciation_date": "2020-12-31", "next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 10
"depreciation_start_date": "2020-06-06"
}) })
if asset.docstatus == 0: if asset.docstatus == 0:
asset.submit() asset.submit()

View File

@ -1084,7 +1084,7 @@
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-07-31 14:13:44.610190", "modified": "2020-09-14 14:36:12.418690",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",
@ -1135,5 +1135,6 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"timeline_field": "supplier", "timeline_field": "supplier",
"title_field": "supplier" "title_field": "supplier",
"track_changes": 1
} }

View File

@ -89,7 +89,7 @@ class TestPurchaseOrder(unittest.TestCase):
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0) frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
def test_update_child_qty_rate(self): def test_update_child(self):
mr = make_material_request(qty=10) mr = make_material_request(qty=10)
po = make_purchase_order(mr.name) po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier" po.supplier = "_Test Supplier"
@ -119,7 +119,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3) self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
def test_add_new_item_in_update_child_qty_rate(self): def test_update_child_adding_new_item(self):
po = create_purchase_order(do_not_save=1) po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4 po.items[0].qty = 4
po.save() po.save()
@ -145,7 +145,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(po.status, 'To Receive and Bill') self.assertEqual(po.status, 'To Receive and Bill')
def test_remove_item_in_update_child_qty_rate(self): def test_update_child_removing_item(self):
po = create_purchase_order(do_not_save=1) po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4 po.items[0].qty = 4
po.save() po.save()
@ -185,7 +185,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(len(po.get('items')), 1) self.assertEquals(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill') self.assertEqual(po.status, 'To Receive and Bill')
def test_update_child_qty_rate_perm(self): def test_update_child_perm(self):
po = create_purchase_order(item_code= "_Test Item", qty=4) po = create_purchase_order(item_code= "_Test Item", qty=4)
user = 'test@example.com' user = 'test@example.com'
@ -202,6 +202,72 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
frappe.set_user("Administrator") frappe.set_user("Administrator")
def test_update_child_with_tax_template(self):
tax_template = "_Test Account Excise Duty @ 10"
item = "_Test Item Home Desktop 100"
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
item_doc = frappe.get_doc("Item", item)
item_doc.append("taxes", {
"item_tax_template": tax_template,
"valid_from": nowdate()
})
item_doc.save()
else:
# update valid from
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = CURDATE()
where parent = %(item)s and item_tax_template = %(tax)s""",
{"item": item, "tax": tax_template})
po = create_purchase_order(item_code=item, qty=1, do_not_save=1)
po.append("taxes", {
"account_head": "_Test Account Excise Duty - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "Excise Duty",
"doctype": "Purchase Taxes and Charges",
"rate": 10
})
po.insert()
po.submit()
self.assertEqual(po.taxes[0].tax_amount, 50)
self.assertEqual(po.taxes[0].total, 550)
items = json.dumps([
{'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name},
{'item_code' : item, 'rate' : 100, 'qty' : 1} # added item
])
update_child_qty_rate('Purchase Order', items, po.name)
po.reload()
self.assertEqual(po.taxes[0].tax_amount, 60)
self.assertEqual(po.taxes[0].total, 660)
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
where parent = %(item)s and item_tax_template = %(tax)s""",
{"item": item, "tax": tax_template})
def test_update_child_uom_conv_factor_change(self):
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
trans_item = json.dumps([{
'item_code': po.get("items")[0].item_code,
'rate': po.get("items")[0].rate,
'qty': po.get("items")[0].qty,
'uom': "_Test UOM 1",
'conversion_factor': 2,
'docname': po.get("items")[0].name
}])
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
total_reqd_qty_after_change = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty)
def test_update_qty(self): def test_update_qty(self):
po = create_purchase_order() po = create_purchase_order()

View File

@ -148,11 +148,11 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-03-12 15:43:53.862897", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item Supplied", "name": "Purchase Order Item Supplied",
"owner": "dhanalekshmi@webnotestech.com", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC"

View File

@ -188,11 +188,11 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-04-10 18:09:33.997618", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Receipt Item Supplied", "name": "Purchase Receipt Item Supplied",
"owner": "wasim@webnotestech.com", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",

View File

@ -178,6 +178,7 @@ def make_all_scorecards(docname):
period_card = make_supplier_scorecard(docname, None) period_card = make_supplier_scorecard(docname, None)
period_card.start_date = start_date period_card.start_date = start_date
period_card.end_date = end_date period_card.end_date = end_date
period_card.insert(ignore_permissions=True)
period_card.submit() period_card.submit()
scp_count = scp_count + 1 scp_count = scp_count + 1
if start_date < first_start_date: if start_date < first_start_date:

View File

@ -106,7 +106,7 @@ def make_supplier_scorecard(source_name, target_doc=None):
"doctype": "Supplier Scorecard Scoring Criteria", "doctype": "Supplier Scorecard Scoring Criteria",
"postprocess": update_criteria_fields, "postprocess": update_criteria_fields,
} }
}, target_doc, post_process) }, target_doc, post_process, ignore_permissions=True)
return doc return doc

View File

@ -7,6 +7,7 @@ import json
from frappe import _, throw from frappe import _, throw
from frappe.utils import (today, flt, cint, fmt_money, formatdate, from frappe.utils import (today, flt, cint, fmt_money, formatdate,
getdate, add_days, add_months, get_last_day, nowdate, get_link_to_form) getdate, add_days, add_months, get_last_day, nowdate, get_link_to_form)
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied, WorkflowPermissionError
from erpnext.stock.get_item_details import get_conversion_factor, get_item_details from erpnext.stock.get_item_details import get_conversion_factor, get_item_details
from erpnext.setup.utils import get_exchange_rate from erpnext.setup.utils import get_exchange_rate
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
@ -19,7 +20,7 @@ from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_t
from erpnext.exceptions import InvalidCurrency from erpnext.exceptions import InvalidCurrency
from six import text_type from six import text_type
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
from erpnext.stock.get_item_details import get_item_warehouse from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules") force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
@ -1157,6 +1158,18 @@ def get_supplier_block_status(party_name):
} }
return info return info
def set_child_tax_template_and_map(item, child_item, parent_doc):
args = {
'item_code': item.item_code,
'posting_date': parent_doc.transaction_date,
'tax_category': parent_doc.get('tax_category'),
'company': parent_doc.get('company')
}
child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
if child_item.get("item_tax_template"):
child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item): def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
""" """
Returns a Sales Order Item child item containing the default values Returns a Sales Order Item child item containing the default values
@ -1168,8 +1181,10 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname,
child_item.item_name = item.item_name child_item.item_name = item.item_name
child_item.description = item.description child_item.description = item.description
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.uom = trans_item.get("uom") or item.stock_uom
child_item.uom = item.stock_uom conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
set_child_tax_template_and_map(item, child_item, p_doc)
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse: if not child_item.warehouse:
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
@ -1188,13 +1203,15 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
child_item.item_name = item.item_name child_item.item_name = item.item_name
child_item.description = item.description child_item.description = item.description
child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.uom = trans_item.get("uom") or item.stock_uom
child_item.uom = item.stock_uom conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
child_item.base_rate = 1 # Initiallize value will update in parent validation child_item.base_rate = 1 # Initiallize value will update in parent validation
child_item.base_amount = 1 # Initiallize value will update in parent validation child_item.base_amount = 1 # Initiallize value will update in parent validation
set_child_tax_template_and_map(item, child_item, p_doc)
return child_item return child_item
def check_and_delete_children(parent, data): def validate_and_delete_children(parent, data):
deleted_children = [] deleted_children = []
updated_item_names = [d.get("docname") for d in data] updated_item_names = [d.get("docname") for d in data]
for item in parent.items: for item in parent.items:
@ -1221,18 +1238,40 @@ def check_and_delete_children(parent, data):
@frappe.whitelist() @frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
def check_permissions(doc, perm_type='create'): def check_doc_permissions(doc, perm_type='create'):
try: try:
doc.check_permission(perm_type) doc.check_permission(perm_type)
except: except frappe.PermissionError:
action = "add" if perm_type == 'create' else "update" actions = { 'create': 'add', 'write': 'update', 'cancel': 'remove' }
frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions"))
frappe.throw(_("You do not have permissions to {} items in a {}.")
.format(actions[perm_type], parent_doctype), title=_("Insufficient Permissions"))
def validate_workflow_conditions(doc):
workflow = get_workflow_name(doc.doctype)
if not workflow:
return
workflow_doc = frappe.get_doc("Workflow", workflow)
current_state = doc.get(workflow_doc.workflow_state_field)
roles = frappe.get_roles()
transitions = []
for transition in workflow_doc.transitions:
if transition.next_state == current_state and transition.allowed in roles:
if not is_transition_condition_satisfied(transition, doc):
continue
transitions.append(transition.as_dict())
if not transitions:
frappe.throw(
_("You are not allowed to update as per the conditions set in {} Workflow.").format(get_link_to_form("Workflow", workflow)),
title=_("Insufficient Permissions")
)
def get_new_child_item(item_row): def get_new_child_item(item_row):
if parent_doctype == "Sales Order": new_child_function = set_sales_order_defaults if parent_doctype == "Sales Order" else set_purchase_order_defaults
return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row) return new_child_function(parent_doctype, parent_doctype_name, child_docname, item_row)
if parent_doctype == "Purchase Order":
return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
def validate_quantity(child_item, d): def validate_quantity(child_item, d):
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
@ -1246,21 +1285,23 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation'] sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
parent = frappe.get_doc(parent_doctype, parent_doctype_name) parent = frappe.get_doc(parent_doctype, parent_doctype_name)
check_and_delete_children(parent, data) check_doc_permissions(parent, 'cancel')
validate_and_delete_children(parent, data)
for d in data: for d in data:
new_child_flag = False new_child_flag = False
if not d.get("docname"): if not d.get("docname"):
new_child_flag = True new_child_flag = True
check_permissions(parent, 'create') check_doc_permissions(parent, 'create')
child_item = get_new_child_item(d) child_item = get_new_child_item(d)
else: else:
check_permissions(parent, 'write') check_doc_permissions(parent, 'write')
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate")) prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty")) prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor")) prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
prev_uom, new_uom = child_item.get("uom"), d.get("uom")
if parent_doctype == 'Sales Order': if parent_doctype == 'Sales Order':
prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date") prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
@ -1269,9 +1310,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
rate_unchanged = prev_rate == new_rate rate_unchanged = prev_rate == new_rate
qty_unchanged = prev_qty == new_qty qty_unchanged = prev_qty == new_qty
uom_unchanged = prev_uom == new_uom
conversion_factor_unchanged = prev_con_fac == new_con_fac conversion_factor_unchanged = prev_con_fac == new_con_fac
date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged: if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged:
continue continue
validate_quantity(child_item, d) validate_quantity(child_item, d)
@ -1291,6 +1333,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
else: else:
child_item.conversion_factor = flt(d.get('conversion_factor')) child_item.conversion_factor = flt(d.get('conversion_factor'))
if d.get("uom"):
child_item.uom = d.get("uom")
conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(d.get('conversion_factor')) or conversion_factor
if d.get("delivery_date") and parent_doctype == 'Sales Order': if d.get("delivery_date") and parent_doctype == 'Sales Order':
child_item.delivery_date = d.get('delivery_date') child_item.delivery_date = d.get('delivery_date')
@ -1356,12 +1403,17 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
parent.update_receiving_percentage() parent.update_receiving_percentage()
if parent.is_subcontracted == "Yes": if parent.is_subcontracted == "Yes":
parent.update_reserved_qty_for_subcontract() parent.update_reserved_qty_for_subcontract()
parent.create_raw_materials_supplied("supplied_items")
parent.save()
else: else:
parent.update_reserved_qty() parent.update_reserved_qty()
parent.update_project() parent.update_project()
parent.update_prevdoc_status('submit') parent.update_prevdoc_status('submit')
parent.update_delivery_status() parent.update_delivery_status()
parent.reload()
validate_workflow_conditions(parent)
parent.update_blanket_order() parent.update_blanket_order()
parent.update_billing_percentage() parent.update_billing_percentage()
parent.set_status() parent.set_status()

View File

@ -165,9 +165,14 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
AND company = %(company)s AND company = %(company)s
AND account_currency = %(currency)s AND account_currency = %(currency)s
AND `{searchfield}` LIKE %(txt)s AND `{searchfield}` LIKE %(txt)s
{mcond}
ORDER BY idx DESC, name ORDER BY idx DESC, name
LIMIT %(offset)s, %(limit)s LIMIT %(offset)s, %(limit)s
""".format(account_type_condition=account_type_condition, searchfield=searchfield), """.format(
account_type_condition=account_type_condition,
searchfield=searchfield,
mcond=get_match_cond(doctype)
),
dict( dict(
account_types=filters.get("account_type"), account_types=filters.get("account_type"),
company=filters.get("company"), company=filters.get("company"),
@ -359,9 +364,21 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
if filters.get("is_return"): if filters.get("is_return"):
having_clause = "" having_clause = ""
meta = frappe.get_meta("Batch", cached=True)
searchfields = meta.get_search_fields()
search_columns = ''
if searchfields:
search_columns = ", " + ", ".join(searchfields)
if args.get('warehouse'): if args.get('warehouse'):
searchfields = ['batch.' + field for field in searchfields]
if searchfields:
search_columns = ", " + ", ".join(searchfields)
batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date) concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
{search_columns}
from `tabStock Ledger Entry` sle from `tabStock Ledger Entry` sle
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
where where
@ -377,6 +394,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
group by batch_no {having_clause} group by batch_no {having_clause}
order by batch.expiry_date, sle.batch_no desc order by batch.expiry_date, sle.batch_no desc
limit %(start)s, %(page_len)s""".format( limit %(start)s, %(page_len)s""".format(
search_columns = search_columns,
cond=cond, cond=cond,
match_conditions=get_match_cond(doctype), match_conditions=get_match_cond(doctype),
having_clause = having_clause having_clause = having_clause
@ -384,7 +402,9 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
return batch_nos return batch_nos
else: else:
return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date) from `tabBatch` batch return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date)
{search_columns}
from `tabBatch` batch
where batch.disabled = 0 where batch.disabled = 0
and item = %(item_code)s and item = %(item_code)s
and (name like %(txt)s and (name like %(txt)s
@ -394,7 +414,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
{0} {0}
{match_conditions} {match_conditions}
order by expiry_date, name desc order by expiry_date, name desc
limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args) limit %(start)s, %(page_len)s""".format(cond, search_columns = search_columns, match_conditions=get_match_cond(doctype)), args)
@frappe.whitelist() @frappe.whitelist()

View File

@ -242,7 +242,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
'type': data.type, 'type': data.type,
'amount': -1 * paid_amount, 'amount': -1 * paid_amount,
'base_amount': -1 * base_paid_amount, 'base_amount': -1 * base_paid_amount,
'account': data.account 'account': data.account,
'default': data.default
}) })
if doc.is_pos: if doc.is_pos:
doc.paid_amount = -1 * source.paid_amount doc.paid_amount = -1 * source.paid_amount

View File

@ -81,6 +81,7 @@ class SellingController(StockController):
party_details = _get_party_details(customer, party_details = _get_party_details(customer,
ignore_permissions=self.flags.ignore_permissions, ignore_permissions=self.flags.ignore_permissions,
doctype=self.doctype, company=self.company, doctype=self.doctype, company=self.company,
posting_date=self.get('posting_date'),
fetch_payment_terms_template=fetch_payment_terms_template, fetch_payment_terms_template=fetch_payment_terms_template,
party_address=self.customer_address, shipping_address=self.shipping_address_name) party_address=self.customer_address, shipping_address=self.shipping_address_name)
if not self.meta.get_field("sales_team"): if not self.meta.get_field("sales_team"):

View File

@ -8,7 +8,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Reports", "label": "Reports",
"links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Prospects Engaged But Not Converted\",\n \"name\": \"Prospects Engaged But Not Converted\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Opportunity\"\n ],\n \"doctype\": \"Opportunity\",\n \"is_query_report\": true,\n \"label\": \"Minutes to First Response for Opportunity\",\n \"name\": \"Minutes to First Response for Opportunity\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Campaign Efficiency\",\n \"name\": \"Campaign Efficiency\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Owner Efficiency\",\n \"name\": \"Lead Owner Efficiency\",\n \"type\": \"report\"\n }\n]" "links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Prospects Engaged But Not Converted\",\n \"name\": \"Prospects Engaged But Not Converted\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Opportunity\"\n ],\n \"doctype\": \"Opportunity\",\n \"is_query_report\": true,\n \"label\": \"First Response Time for Opportunity\",\n \"name\": \"First Response Time for Opportunity\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Campaign Efficiency\",\n \"name\": \"Campaign Efficiency\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Owner Efficiency\",\n \"name\": \"Lead Owner Efficiency\",\n \"type\": \"report\"\n }\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -42,7 +42,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "CRM", "label": "CRM",
"modified": "2020-05-28 13:33:52.906750", "modified": "2020-08-11 18:55:18.238900",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "CRM", "name": "CRM",

View File

@ -24,7 +24,7 @@
"converted_by", "converted_by",
"sales_stage", "sales_stage",
"order_lost_reason", "order_lost_reason",
"mins_to_first_response", "first_response_time",
"expected_closing", "expected_closing",
"next_contact", "next_contact",
"contact_by", "contact_by",
@ -152,13 +152,6 @@
"no_copy": 1, "no_copy": 1,
"read_only": 1 "read_only": 1
}, },
{
"bold": 1,
"fieldname": "mins_to_first_response",
"fieldtype": "Float",
"label": "Mins to first response",
"read_only": 1
},
{ {
"fieldname": "expected_closing", "fieldname": "expected_closing",
"fieldtype": "Date", "fieldtype": "Date",
@ -419,12 +412,19 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Converted By", "label": "Converted By",
"options": "User" "options": "User"
},
{
"bold": 1,
"fieldname": "first_response_time",
"fieldtype": "Duration",
"label": "First Response Time",
"read_only": 1
} }
], ],
"icon": "fa fa-info-sign", "icon": "fa fa-info-sign",
"idx": 195, "idx": 195,
"links": [], "links": [],
"modified": "2020-08-11 17:34:35.066961", "modified": "2020-08-12 17:34:35.066961",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Opportunity", "name": "Opportunity",

View File

@ -1,33 +1,44 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Minutes to First Response for Opportunity"] = { frappe.query_reports["First Response Time for Opportunity"] = {
"filters": [ "filters": [
{ {
"fieldname": "from_date", "fieldname": "from_date",
"label": __("From Date"), "label": __("From Date"),
"fieldtype": "Date", "fieldtype": "Date",
'reqd': 1, "reqd": 1,
"default": frappe.datetime.add_days(frappe.datetime.nowdate(), -30) "default": frappe.datetime.add_days(frappe.datetime.nowdate(), -30)
}, },
{ {
"fieldname": "to_date", "fieldname": "to_date",
"label": __("To Date"), "label": __("To Date"),
"fieldtype": "Date", "fieldtype": "Date",
'reqd': 1, "reqd": 1,
"default": frappe.datetime.nowdate() "default": frappe.datetime.nowdate()
}, },
], ],
get_chart_data: function (columns, result) { get_chart_data: function (_columns, result) {
return { return {
data: { data: {
labels: result.map(d => d[0]), labels: result.map(d => d[0]),
datasets: [{ datasets: [{
name: 'Mins to first response', name: "First Response Time",
values: result.map(d => d[1]) values: result.map(d => d[1])
}] }]
}, },
type: 'line', type: "line",
tooltipOptions: {
formatTooltipY: d => {
let duration_options = {
hide_days: 0,
hide_seconds: 0
};
value = frappe.utils.get_formatted_duration(d, duration_options);
return value;
}
}
} }
} }
} };

View File

@ -0,0 +1,28 @@
{
"add_total_row": 0,
"creation": "2020-08-10 18:34:19.083872",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"letter_head": "Test 2",
"modified": "2020-08-10 18:34:19.083872",
"modified_by": "Administrator",
"module": "CRM",
"name": "First Response Time for Opportunity",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Opportunity",
"report_name": "First Response Time for Opportunity",
"report_type": "Script Report",
"roles": [
{
"role": "Sales User"
},
{
"role": "Sales Manager"
}
]
}

View File

@ -0,0 +1,35 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
def execute(filters=None):
columns = [
{
'fieldname': 'creation_date',
'label': 'Date',
'fieldtype': 'Date',
'width': 300
},
{
'fieldname': 'first_response_time',
'fieldtype': 'Duration',
'label': 'First Response Time',
'width': 300
},
]
data = frappe.db.sql('''
SELECT
date(creation) as creation_date,
avg(first_response_time) as avg_response_time
FROM tabOpportunity
WHERE
date(creation) between %s and %s
and first_response_time > 0
GROUP BY creation_date
ORDER BY creation_date desc
''', (filters.from_date, filters.to_date))
return columns, data

View File

@ -1,26 +0,0 @@
{
"add_total_row": 0,
"apply_user_permissions": 0,
"creation": "2016-06-17 11:28:25.867258",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 2,
"is_standard": "Yes",
"modified": "2017-02-24 20:06:08.801109",
"modified_by": "Administrator",
"module": "CRM",
"name": "Minutes to First Response for Opportunity",
"owner": "Administrator",
"ref_doctype": "Opportunity",
"report_name": "Minutes to First Response for Opportunity",
"report_type": "Script Report",
"roles": [
{
"role": "Sales User"
},
{
"role": "Sales Manager"
}
]
}

View File

@ -1,28 +0,0 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
def execute(filters=None):
columns = [
{
'fieldname': 'creation_date',
'label': 'Date',
'fieldtype': 'Date'
},
{
'fieldname': 'mins',
'fieldtype': 'Float',
'label': 'Mins to First Response'
},
]
data = frappe.db.sql('''select date(creation) as creation_date,
avg(mins_to_first_response) as mins
from tabOpportunity
where date(creation) between %s and %s
and mins_to_first_response > 0
group by creation_date order by creation_date desc''', (filters.from_date, filters.to_date))
return columns, data

View File

@ -3,15 +3,15 @@
frappe.ui.form.on('Student', { frappe.ui.form.on('Student', {
setup: function(frm) { setup: function(frm) {
frm.add_fetch("guardian", "guardian_name", "guardian_name"); frm.add_fetch('guardian', 'guardian_name', 'guardian_name');
frm.add_fetch("student", "title", "full_name"); frm.add_fetch('student', 'title', 'full_name');
frm.add_fetch("student", "gender", "gender"); frm.add_fetch('student', 'gender', 'gender');
frm.add_fetch("student", "date_of_birth", "date_of_birth"); frm.add_fetch('student', 'date_of_birth', 'date_of_birth');
frm.set_query("student", "siblings", function(doc, cdt, cdn) { frm.set_query('student', 'siblings', function(doc) {
return { return {
"filters": { 'filters': {
"name": ["!=", doc.name] 'name': ['!=', doc.name]
} }
}; };
}) })
@ -25,6 +25,12 @@ frappe.ui.form.on('Student', {
{party_type:'Student', party:frm.doc.name}); {party_type:'Student', party:frm.doc.name});
}); });
} }
frappe.db.get_value('Education Settings', {name: 'Education Settings'}, 'user_creation_skip', (r) => {
if (cint(r.user_creation_skip) !== 1) {
frm.set_df_property('student_email_id', 'reqd', 1);
}
});
} }
}); });

View File

@ -102,7 +102,6 @@
"fieldtype": "Data", "fieldtype": "Data",
"in_global_search": 1, "in_global_search": 1,
"label": "Student Email Address", "label": "Student Email Address",
"reqd": 1,
"unique": 1 "unique": 1
}, },
{ {
@ -255,7 +254,7 @@
], ],
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"modified": "2020-07-23 18:14:06.366442", "modified": "2020-09-07 19:28:08.914568",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Student", "name": "Student",

View File

@ -7,7 +7,7 @@ frappe.ui.form.on("Student Applicant", {
}, },
refresh: function(frm) { refresh: function(frm) {
if(frm.doc.application_status== "Applied" && frm.doc.docstatus== 1 ) { if (frm.doc.application_status==="Applied" && frm.doc.docstatus===1 ) {
frm.add_custom_button(__("Approve"), function() { frm.add_custom_button(__("Approve"), function() {
frm.set_value("application_status", "Approved"); frm.set_value("application_status", "Approved");
frm.save_or_update(); frm.save_or_update();
@ -20,10 +20,11 @@ frappe.ui.form.on("Student Applicant", {
}, 'Actions'); }, 'Actions');
} }
if(frm.doc.application_status== "Approved" && frm.doc.docstatus== 1 ) { if (frm.doc.application_status === "Approved" && frm.doc.docstatus === 1) {
frm.add_custom_button(__("Enroll"), function() { frm.add_custom_button(__("Enroll"), function() {
frm.events.enroll(frm) frm.events.enroll(frm)
}).addClass("btn-primary"); }).addClass("btn-primary");
frm.add_custom_button(__("Reject"), function() { frm.add_custom_button(__("Reject"), function() {
frm.set_value("application_status", "Rejected"); frm.set_value("application_status", "Rejected");
frm.save_or_update(); frm.save_or_update();
@ -35,7 +36,13 @@ frappe.ui.form.on("Student Applicant", {
frappe.hide_msgprint(true); frappe.hide_msgprint(true);
frappe.show_progress(__("Enrolling student"), data.progress[0],data.progress[1]); frappe.show_progress(__("Enrolling student"), data.progress[0],data.progress[1]);
} }
}) });
frappe.db.get_value("Education Settings", {name: "Education Settings"}, "user_creation_skip", (r) => {
if (cint(r.user_creation_skip) !== 1) {
frm.set_df_property("student_email_id", "reqd", 1);
}
});
}, },
enroll: function(frm) { enroll: function(frm) {

View File

@ -2,81 +2,90 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals import plaid
from frappe import _ import requests
from frappe.utils.password import get_decrypted_password from plaid.errors import APIError, ItemError, InvalidRequestError
from plaid import Client
from plaid.errors import APIError, ItemError
import frappe import frappe
import requests from frappe import _
class PlaidConnector(): class PlaidConnector():
def __init__(self, access_token=None): def __init__(self, access_token=None):
plaid_settings = frappe.get_single("Plaid Settings")
self.config = {
"plaid_client_id": plaid_settings.plaid_client_id,
"plaid_secret": get_decrypted_password("Plaid Settings", "Plaid Settings", 'plaid_secret'),
"plaid_public_key": plaid_settings.plaid_public_key,
"plaid_env": plaid_settings.plaid_env
}
self.client = Client(client_id=self.config.get("plaid_client_id"),
secret=self.config.get("plaid_secret"),
public_key=self.config.get("plaid_public_key"),
environment=self.config.get("plaid_env")
)
self.access_token = access_token self.access_token = access_token
self.settings = frappe.get_single("Plaid Settings")
self.products = ["auth", "transactions"]
self.client_name = frappe.local.site
self.client = plaid.Client(
client_id=self.settings.plaid_client_id,
secret=self.settings.get_password("plaid_secret"),
environment=self.settings.plaid_env,
api_version="2019-05-29"
)
def get_access_token(self, public_token): def get_access_token(self, public_token):
if public_token is None: if public_token is None:
frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error")) frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error"))
response = self.client.Item.public_token.exchange(public_token) response = self.client.Item.public_token.exchange(public_token)
access_token = response['access_token'] access_token = response["access_token"]
return access_token return access_token
def get_link_token(self):
token_request = {
"client_name": self.client_name,
"client_id": self.settings.plaid_client_id,
"secret": self.settings.plaid_secret,
"products": self.products,
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
"country_codes": ["US", "CA", "FR", "IE", "NL", "ES", "GB"],
"user": {
"client_user_id": frappe.generate_hash(frappe.session.user, length=32)
}
}
try:
response = self.client.LinkToken.create(token_request)
except InvalidRequestError:
frappe.log_error(frappe.get_traceback(), _("Plaid invalid request error"))
frappe.msgprint(_("Please check your Plaid client ID and secret values"))
except APIError as e:
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
frappe.throw(_(str(e)), title=_("Authentication Failed"))
else:
return response["link_token"]
def auth(self): def auth(self):
try: try:
self.client.Auth.get(self.access_token) self.client.Auth.get(self.access_token)
print("Authentication successful.....")
except ItemError as e: except ItemError as e:
if e.code == 'ITEM_LOGIN_REQUIRED': if e.code == "ITEM_LOGIN_REQUIRED":
pass
else:
pass pass
except APIError as e: except APIError as e:
if e.code == 'PLANNED_MAINTENANCE': if e.code == "PLANNED_MAINTENANCE":
pass
else:
pass pass
except requests.Timeout: except requests.Timeout:
pass pass
except Exception as e: except Exception as e:
print(e)
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error")) frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
frappe.msgprint({"title": _("Authentication Failed"), "message":e, "raise_exception":1, "indicator":'red'}) frappe.throw(_(str(e)), title=_("Authentication Failed"))
def get_transactions(self, start_date, end_date, account_id=None): def get_transactions(self, start_date, end_date, account_id=None):
self.auth()
kwargs = dict(
access_token=self.access_token,
start_date=start_date,
end_date=end_date
)
if account_id:
kwargs.update(dict(account_ids=[account_id]))
try: try:
self.auth() response = self.client.Transactions.get(**kwargs)
if account_id: transactions = response["transactions"]
account_ids = [account_id] while len(transactions) < response["total_transactions"]:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids)
else:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date)
transactions = response['transactions']
while len(transactions) < response['total_transactions']:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions)) response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
transactions.extend(response['transactions']) transactions.extend(response["transactions"])
return transactions return transactions
except Exception: except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))

View File

@ -4,14 +4,14 @@
frappe.provide("erpnext.integrations"); frappe.provide("erpnext.integrations");
frappe.ui.form.on('Plaid Settings', { frappe.ui.form.on('Plaid Settings', {
enabled: function(frm) { enabled: function (frm) {
frm.toggle_reqd('plaid_client_id', frm.doc.enabled); frm.toggle_reqd('plaid_client_id', frm.doc.enabled);
frm.toggle_reqd('plaid_secret', frm.doc.enabled); frm.toggle_reqd('plaid_secret', frm.doc.enabled);
frm.toggle_reqd('plaid_public_key', frm.doc.enabled);
frm.toggle_reqd('plaid_env', frm.doc.enabled); frm.toggle_reqd('plaid_env', frm.doc.enabled);
}, },
refresh: function(frm) {
if(frm.doc.enabled) { refresh: function (frm) {
if (frm.doc.enabled) {
frm.add_custom_button('Link a new bank account', () => { frm.add_custom_button('Link a new bank account', () => {
new erpnext.integrations.plaidLink(frm); new erpnext.integrations.plaidLink(frm);
}); });
@ -22,17 +22,16 @@ frappe.ui.form.on('Plaid Settings', {
erpnext.integrations.plaidLink = class plaidLink { erpnext.integrations.plaidLink = class plaidLink {
constructor(parent) { constructor(parent) {
this.frm = parent; this.frm = parent;
this.product = ["transactions", "auth"];
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js'; this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
this.init_config(); this.init_config();
} }
init_config() { async init_config() {
const me = this; this.product = ["auth", "transactions"];
me.plaid_env = me.frm.doc.plaid_env; this.plaid_env = this.frm.doc.plaid_env;
me.plaid_public_key = me.frm.doc.plaid_public_key; this.client_name = frappe.boot.sitename;
me.client_name = frappe.boot.sitename; this.token = await this.frm.call("get_link_token").then(resp => resp.message);
me.init_plaid(); this.init_plaid();
} }
init_plaid() { init_plaid() {
@ -69,17 +68,17 @@ erpnext.integrations.plaidLink = class plaidLink {
} }
onScriptLoaded(me) { onScriptLoaded(me) {
me.linkHandler = window.Plaid.create({ me.linkHandler = Plaid.create({
clientName: me.client_name, clientName: me.client_name,
product: me.product,
env: me.plaid_env, env: me.plaid_env,
key: me.plaid_public_key, token: me.token,
onSuccess: me.plaid_success, onSuccess: me.plaid_success
product: me.product
}); });
} }
onScriptError(error) { onScriptError(error) {
frappe.msgprint('There was an issue loading the link-initialize.js script'); frappe.msgprint("There was an issue connecting to Plaid's authentication server");
frappe.msgprint(error); frappe.msgprint(error);
} }
@ -87,21 +86,25 @@ erpnext.integrations.plaidLink = class plaidLink {
const me = this; const me = this;
frappe.prompt({ frappe.prompt({
fieldtype:"Link", fieldtype: "Link",
options: "Company", options: "Company",
label:__("Company"), label: __("Company"),
fieldname:"company", fieldname: "company",
reqd:1 reqd: 1
}, (data) => { }, (data) => {
me.company = data.company; me.company = data.company;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response}) frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {
.then((result) => { token: token,
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response, response: response
bank: result, company: me.company}); }).then((result) => {
}) frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {
.then(() => { response: response,
frappe.show_alert({message:__("Bank accounts added"), indicator:'green'}); bank: result,
company: me.company
}); });
}).then(() => {
frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' });
});
}, __("Select a company"), __("Continue")); }, __("Select a company"), __("Continue"));
} }
}; };

View File

@ -1,5 +1,4 @@
{ {
"actions": [],
"creation": "2018-10-25 10:02:48.656165", "creation": "2018-10-25 10:02:48.656165",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@ -12,7 +11,6 @@
"plaid_client_id", "plaid_client_id",
"plaid_secret", "plaid_secret",
"column_break_7", "column_break_7",
"plaid_public_key",
"plaid_env" "plaid_env"
], ],
"fields": [ "fields": [
@ -41,12 +39,6 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Plaid Secret" "label": "Plaid Secret"
}, },
{
"fieldname": "plaid_public_key",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Plaid Public Key"
},
{ {
"fieldname": "plaid_env", "fieldname": "plaid_env",
"fieldtype": "Select", "fieldtype": "Select",
@ -69,8 +61,7 @@
} }
], ],
"issingle": 1, "issingle": 1,
"links": [], "modified": "2020-09-12 02:31:44.542385",
"modified": "2020-02-07 15:21:11.616231",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "ERPNext Integrations", "module": "ERPNext Integrations",
"name": "Plaid Settings", "name": "Plaid Settings",

View File

@ -2,30 +2,36 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json import json
from frappe import _
from frappe.model.document import Document import frappe
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector
from frappe.utils import getdate, formatdate, today, add_months from frappe import _
from frappe.desk.doctype.tag.tag import add_tag from frappe.desk.doctype.tag.tag import add_tag
from frappe.model.document import Document
from frappe.utils import add_months, formatdate, getdate, today
class PlaidSettings(Document): class PlaidSettings(Document):
pass @staticmethod
def get_link_token():
plaid = PlaidConnector()
return plaid.get_link_token()
@frappe.whitelist() @frappe.whitelist()
def plaid_configuration(): def get_plaid_configuration():
if frappe.db.get_single_value("Plaid Settings", "enabled"): if frappe.db.get_single_value("Plaid Settings", "enabled"):
plaid_settings = frappe.get_single("Plaid Settings") plaid_settings = frappe.get_single("Plaid Settings")
return { return {
"plaid_public_key": plaid_settings.plaid_public_key,
"plaid_env": plaid_settings.plaid_env, "plaid_env": plaid_settings.plaid_env,
"link_token": plaid_settings.get_link_token(),
"client_name": frappe.local.site "client_name": frappe.local.site
} }
else:
return "disabled" return "disabled"
@frappe.whitelist() @frappe.whitelist()
def add_institution(token, response): def add_institution(token, response):
@ -33,6 +39,7 @@ def add_institution(token, response):
plaid = PlaidConnector() plaid = PlaidConnector()
access_token = plaid.get_access_token(token) access_token = plaid.get_access_token(token)
bank = None
if not frappe.db.exists("Bank", response["institution"]["name"]): if not frappe.db.exists("Bank", response["institution"]["name"]):
try: try:
@ -44,7 +51,6 @@ def add_institution(token, response):
bank.insert() bank.insert()
except Exception: except Exception:
frappe.throw(frappe.get_traceback()) frappe.throw(frappe.get_traceback())
else: else:
bank = frappe.get_doc("Bank", response["institution"]["name"]) bank = frappe.get_doc("Bank", response["institution"]["name"])
bank.plaid_access_token = access_token bank.plaid_access_token = access_token
@ -52,6 +58,7 @@ def add_institution(token, response):
return bank return bank
@frappe.whitelist() @frappe.whitelist()
def add_bank_accounts(response, bank, company): def add_bank_accounts(response, bank, company):
try: try:
@ -92,9 +99,8 @@ def add_bank_accounts(response, bank, company):
new_account.insert() new_account.insert()
result.append(new_account.name) result.append(new_account.name)
except frappe.UniqueValidationError: except frappe.UniqueValidationError:
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(new_account.account_name)) frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
except Exception: except Exception:
frappe.throw(frappe.get_traceback()) frappe.throw(frappe.get_traceback())
@ -103,6 +109,7 @@ def add_bank_accounts(response, bank, company):
return result return result
def add_account_type(account_type): def add_account_type(account_type):
try: try:
frappe.get_doc({ frappe.get_doc({
@ -122,10 +129,11 @@ def add_account_subtype(account_subtype):
except Exception: except Exception:
frappe.throw(frappe.get_traceback()) frappe.throw(frappe.get_traceback())
@frappe.whitelist() @frappe.whitelist()
def sync_transactions(bank, bank_account): def sync_transactions(bank, bank_account):
'''Sync transactions based on the last integration date as the start date, after the sync is completed """Sync transactions based on the last integration date as the start date, after sync is completed
add the transaction date of the oldest transaction as the last integration date''' add the transaction date of the oldest transaction as the last integration date."""
last_transaction_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date") last_transaction_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
if last_transaction_date: if last_transaction_date:
start_date = formatdate(last_transaction_date, "YYYY-MM-dd") start_date = formatdate(last_transaction_date, "YYYY-MM-dd")
@ -147,10 +155,10 @@ def sync_transactions(bank, bank_account):
len(result), bank_account, start_date, end_date)) len(result), bank_account, start_date, end_date))
frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date) frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date)
except Exception: except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
def get_transactions(bank, bank_account=None, start_date=None, end_date=None): def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
access_token = None access_token = None
@ -168,6 +176,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
return transactions return transactions
def new_bank_transaction(transaction): def new_bank_transaction(transaction):
result = [] result = []
@ -182,8 +191,8 @@ def new_bank_transaction(transaction):
status = "Pending" if transaction["pending"] == "True" else "Settled" status = "Pending" if transaction["pending"] == "True" else "Settled"
tags = []
try: try:
tags = []
tags += transaction["category"] tags += transaction["category"]
tags += ["Plaid Cat. {}".format(transaction["category_id"])] tags += ["Plaid Cat. {}".format(transaction["category_id"])]
except KeyError: except KeyError:
@ -216,6 +225,7 @@ def new_bank_transaction(transaction):
return result return result
def automatic_synchronization(): def automatic_synchronization():
settings = frappe.get_doc("Plaid Settings", "Plaid Settings") settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
@ -223,4 +233,8 @@ def automatic_synchronization():
plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"]) plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
for plaid_account in plaid_accounts: for plaid_account in plaid_accounts:
frappe.enqueue("erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions", bank=plaid_account.bank, bank_account=plaid_account.name) frappe.enqueue(
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
bank=plaid_account.bank,
bank_account=plaid_account.name
)

View File

@ -1,14 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
from __future__ import unicode_literals
import unittest
import frappe
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import plaid_configuration, add_account_type, add_account_subtype, new_bank_transaction, add_bank_accounts
import json import json
from frappe.utils.response import json_handler import unittest
import frappe
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import (
add_account_subtype, add_account_type, add_bank_accounts,
new_bank_transaction, get_plaid_configuration)
from frappe.utils.response import json_handler
class TestPlaidSettings(unittest.TestCase): class TestPlaidSettings(unittest.TestCase):
def setUp(self): def setUp(self):
@ -31,7 +34,7 @@ class TestPlaidSettings(unittest.TestCase):
def test_plaid_disabled(self): def test_plaid_disabled(self):
frappe.db.set_value("Plaid Settings", None, "enabled", 0) frappe.db.set_value("Plaid Settings", None, "enabled", 0)
self.assertTrue(plaid_configuration() == "disabled") self.assertTrue(get_plaid_configuration() == "disabled")
def test_add_account_type(self): def test_add_account_type(self):
add_account_type("brokerage") add_account_type("brokerage")
@ -64,7 +67,7 @@ class TestPlaidSettings(unittest.TestCase):
'mask': '0000', 'mask': '0000',
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK', 'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'name': 'Plaid Checking' 'name': 'Plaid Checking'
}], }],
'institution': { 'institution': {
'institution_id': 'ins_6', 'institution_id': 'ins_6',
'name': 'Citi' 'name': 'Citi'
@ -100,7 +103,7 @@ class TestPlaidSettings(unittest.TestCase):
'mask': '0000', 'mask': '0000',
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK', 'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'name': 'Plaid Checking' 'name': 'Plaid Checking'
}], }],
'institution': { 'institution': {
'institution_id': 'ins_6', 'institution_id': 'ins_6',
'name': 'Citi' 'name': 'Citi'
@ -152,4 +155,4 @@ class TestPlaidSettings(unittest.TestCase):
new_bank_transaction(transactions) new_bank_transaction(transactions)
self.assertTrue(len(frappe.get_all("Bank Transaction")) == 1) self.assertTrue(len(frappe.get_all("Bank Transaction")) == 1)

View File

@ -258,8 +258,8 @@
} }
], ],
"issingle": 1, "issingle": 1,
"modified": "2020-05-28 12:32:11.384757", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "umair@erpnext.com", "modified_by": "Administrator",
"module": "ERPNext Integrations", "module": "ERPNext Integrations",
"name": "Shopify Settings", "name": "Shopify Settings",
"owner": "Administrator", "owner": "Administrator",

View File

@ -117,12 +117,12 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-05-08 13:45:57.226530", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Healthcare Schedule Time Slot", "name": "Healthcare Schedule Time Slot",
"name_case": "", "name_case": "",
"owner": "rmehta@gmail.com", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "read_only": 0,

View File

@ -34,10 +34,10 @@ frappe.ui.form.on('Lab Test', {
if (frm.doc.docstatus === 1 && frm.doc.status !== 'Approved' && frm.doc.status !== 'Rejected') { if (frm.doc.docstatus === 1 && frm.doc.status !== 'Approved' && frm.doc.status !== 'Rejected') {
frm.add_custom_button(__('Approve'), function () { frm.add_custom_button(__('Approve'), function () {
status_update(1, frm); status_update(1, frm);
}); }, __('Actions'));
frm.add_custom_button(__('Reject'), function () { frm.add_custom_button(__('Reject'), function () {
status_update(0, frm); status_update(0, frm);
}); }, __('Actions'));
} }
} }
@ -179,23 +179,6 @@ var show_lab_tests = function (frm, lab_test_list) {
d.show(); d.show();
}; };
cur_frm.cscript.custom_before_submit = function (doc) {
if (doc.normal_test_items) {
for (let result in doc.normal_test_items) {
if (!doc.normal_test_items[result].result_value && !doc.normal_test_items[result].allow_blank && doc.normal_test_items[result].require_result_value) {
frappe.throw(__('Please input all required result values'));
}
}
}
if (doc.descriptive_test_items) {
for (let result in doc.descriptive_test_items) {
if (!doc.descriptive_test_items[result].result_value && !doc.descriptive_test_items[result].allow_blank && doc.descriptive_test_items[result].require_result_value) {
frappe.throw(__('Please input all required result values'));
}
}
}
};
var make_dialog = function (frm, emailed, printed) { var make_dialog = function (frm, emailed, printed) {
var number = frm.doc.mobile; var number = frm.doc.mobile;
@ -203,7 +186,7 @@ var make_dialog = function (frm, emailed, printed) {
title: 'Send SMS', title: 'Send SMS',
width: 400, width: 400,
fields: [ fields: [
{ fieldname: 'sms_type', fieldtype: 'Select', label: 'Type', options: ['Emailed', 'Printed'] }, { fieldname: 'result_format', fieldtype: 'Select', label: 'Result Format', options: ['Emailed', 'Printed'] },
{ fieldname: 'number', fieldtype: 'Data', label: 'Mobile Number', reqd: 1 }, { fieldname: 'number', fieldtype: 'Data', label: 'Mobile Number', reqd: 1 },
{ fieldname: 'message', fieldtype: 'Small Text', label: 'Message', reqd: 1 } { fieldname: 'message', fieldtype: 'Small Text', label: 'Message', reqd: 1 }
], ],
@ -217,22 +200,22 @@ var make_dialog = function (frm, emailed, printed) {
dialog.hide(); dialog.hide();
} }
}); });
if (frm.doc.report_preference == 'Print') { if (frm.doc.report_preference === 'Print') {
dialog.set_values({ dialog.set_values({
'sms_type': 'Printed', 'result_format': 'Printed',
'number': number, 'number': number,
'message': printed 'message': printed
}); });
} else { } else {
dialog.set_values({ dialog.set_values({
'sms_type': 'Emailed', 'result_format': 'Emailed',
'number': number, 'number': number,
'message': emailed 'message': emailed
}); });
} }
var fd = dialog.fields_dict; var fd = dialog.fields_dict;
$(fd.sms_type.input).change(function () { $(fd.result_format.input).change(function () {
if (dialog.get_value('sms_type') == 'Emailed') { if (dialog.get_value('result_format') === 'Emailed') {
dialog.set_values({ dialog.set_values({
'number': number, 'number': number,
'message': emailed 'message': emailed

View File

@ -84,7 +84,7 @@
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Series", "label": "Series",
"options": "LP-", "options": "HLC-LAB-.YYYY.-",
"print_hide": 1, "print_hide": 1,
"report_hide": 1, "report_hide": 1,
"reqd": 1 "reqd": 1
@ -197,11 +197,10 @@
{ {
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1,
"label": "Status", "label": "Status",
"options": "Draft\nCompleted\nApproved\nRejected\nCancelled", "options": "Draft\nCompleted\nApproved\nRejected\nCancelled",
"print_hide": 1,
"read_only": 1, "read_only": 1,
"report_hide": 1,
"search_index": 1 "search_index": 1
}, },
{ {
@ -249,8 +248,8 @@
{ {
"fieldname": "result_date", "fieldname": "result_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 1,
"label": "Result Date", "label": "Result Date",
"read_only": 1,
"search_index": 1 "search_index": 1
}, },
{ {
@ -354,7 +353,8 @@
}, },
{ {
"fieldname": "sb_normal", "fieldname": "sb_normal",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Compound Test Result"
}, },
{ {
"fieldname": "normal_test_items", "fieldname": "normal_test_items",
@ -369,11 +369,13 @@
{ {
"depends_on": "descriptive_toggle", "depends_on": "descriptive_toggle",
"fieldname": "organisms_section", "fieldname": "organisms_section",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Organism Test Result"
}, },
{ {
"fieldname": "sb_sensitivity", "fieldname": "sb_sensitivity",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Sensitivity Test Result"
}, },
{ {
"fieldname": "sensitivity_test_items", "fieldname": "sensitivity_test_items",
@ -383,8 +385,10 @@
"report_hide": 1 "report_hide": 1
}, },
{ {
"collapsible": 1,
"fieldname": "sb_comments", "fieldname": "sb_comments",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Comments"
}, },
{ {
"fieldname": "lab_test_comment", "fieldname": "lab_test_comment",
@ -531,7 +535,8 @@
}, },
{ {
"fieldname": "sb_descriptive", "fieldname": "sb_descriptive",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Descriptive Test Result"
}, },
{ {
"default": "0", "default": "0",
@ -550,7 +555,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-07-16 13:35:24.811062", "modified": "2020-07-30 18:18:38.516215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Lab Test", "name": "Lab Test",

View File

@ -6,23 +6,24 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import getdate, cstr from frappe.utils import getdate, cstr, get_link_to_form
class LabTest(Document): class LabTest(Document):
def validate(self):
if not self.is_new():
self.set_secondary_uom_result()
def on_submit(self): def on_submit(self):
self.validate_result_values()
self.db_set('submitted_date', getdate()) self.db_set('submitted_date', getdate())
self.db_set('status', 'Completed') self.db_set('status', 'Completed')
insert_lab_test_to_medical_record(self) insert_lab_test_to_medical_record(self)
def on_cancel(self): def on_cancel(self):
delete_lab_test_from_medical_record(self)
self.db_set('status', 'Cancelled') self.db_set('status', 'Cancelled')
delete_lab_test_from_medical_record(self)
self.reload() self.reload()
def validate(self):
if not self.is_new():
self.set_secondary_uom_result()
def on_update(self): def on_update(self):
if self.sensitivity_test_items: if self.sensitivity_test_items:
sensitivity = sorted(self.sensitivity_test_items, key=lambda x: x.antibiotic_sensitivity) sensitivity = sorted(self.sensitivity_test_items, key=lambda x: x.antibiotic_sensitivity)
@ -51,7 +52,20 @@ class LabTest(Document):
item.secondary_uom_result = float(item.result_value) * float(item.conversion_factor) item.secondary_uom_result = float(item.result_value) * float(item.conversion_factor)
except: except:
item.secondary_uom_result = '' item.secondary_uom_result = ''
frappe.msgprint(_('Result for Secondary UOM not calculated for row #{0}'.format(item.idx)), title = _('Warning')) frappe.msgprint(_('Row #{0}: Result for Secondary UOM not calculated'.format(item.idx)), title = _('Warning'))
def validate_result_values(self):
if self.normal_test_items:
for item in self.normal_test_items:
if not item.result_value and not item.allow_blank and item.require_result_value:
frappe.throw(_('Row #{0}: Please enter the result value for {1}').format(
item.idx, frappe.bold(item.lab_test_name)), title=_('Mandatory Results'))
if self.descriptive_test_items:
for item in self.descriptive_test_items:
if not item.result_value and not item.allow_blank and item.require_result_value:
frappe.throw(_('Row #{0}: Please enter the result value for {1}').format(
item.idx, frappe.bold(item.lab_test_particulars)), title=_('Mandatory Results'))
def create_test_from_template(lab_test): def create_test_from_template(lab_test):
@ -89,7 +103,7 @@ def create_multiple(doctype, docname):
lab_test_created = create_lab_test_from_encounter(docname) lab_test_created = create_lab_test_from_encounter(docname)
if lab_test_created: if lab_test_created:
frappe.msgprint(_('Lab Test(s) {0} created'.format(lab_test_created))) frappe.msgprint(_('Lab Test(s) {0} created successfully').format(lab_test_created), indicator='green')
else: else:
frappe.msgprint(_('No Lab Tests created')) frappe.msgprint(_('No Lab Tests created'))
@ -211,8 +225,9 @@ def create_sample_doc(template, patient, invoice, company = None):
'docstatus': 0, 'docstatus': 0,
'sample': template.sample 'sample': template.sample
}) })
if sample_exists: if sample_exists:
# Update Sample Collection by adding quantity # update sample collection by adding quantity
sample_collection = frappe.get_doc('Sample Collection', sample_exists[0][0]) sample_collection = frappe.get_doc('Sample Collection', sample_exists[0][0])
quantity = int(sample_collection.sample_qty) + int(template.sample_qty) quantity = int(sample_collection.sample_qty) + int(template.sample_qty)
if template.sample_details: if template.sample_details:
@ -238,7 +253,7 @@ def create_sample_doc(template, patient, invoice, company = None):
sample_collection.company = company sample_collection.company = company
if template.sample_details: if template.sample_details:
sample_collection.sample_details = 'Test :' + (template.get('lab_test_name') or template.get('template')) + '\n' + 'Collection Detials:\n\t' + template.sample_details sample_collection.sample_details = _('Test :') + (template.get('lab_test_name') or template.get('template')) + '\n' + 'Collection Detials:\n\t' + template.sample_details
sample_collection.save(ignore_permissions=True) sample_collection.save(ignore_permissions=True)
return sample_collection return sample_collection
@ -248,26 +263,31 @@ def create_sample_collection(lab_test, template, patient, invoice):
sample_collection = create_sample_doc(template, patient, invoice, lab_test.company) sample_collection = create_sample_doc(template, patient, invoice, lab_test.company)
if sample_collection: if sample_collection:
lab_test.sample = sample_collection.name lab_test.sample = sample_collection.name
sample_collection_doc = get_link_to_form('Sample Collection', sample_collection.name)
frappe.msgprint(_('Sample Collection {0} has been created').format(sample_collection_doc),
title=_('Sample Collection'), indicator='green')
return lab_test return lab_test
def load_result_format(lab_test, template, prescription, invoice): def load_result_format(lab_test, template, prescription, invoice):
if template.lab_test_template_type == 'Single': if template.lab_test_template_type == 'Single':
create_normals(template, lab_test) create_normals(template, lab_test)
elif template.lab_test_template_type == 'Compound': elif template.lab_test_template_type == 'Compound':
create_compounds(template, lab_test, False) create_compounds(template, lab_test, False)
elif template.lab_test_template_type == 'Descriptive': elif template.lab_test_template_type == 'Descriptive':
create_descriptives(template, lab_test) create_descriptives(template, lab_test)
elif template.lab_test_template_type == 'Grouped': elif template.lab_test_template_type == 'Grouped':
# Iterate for each template in the group and create one result for all. # Iterate for each template in the group and create one result for all.
for lab_test_group in template.lab_test_groups: for lab_test_group in template.lab_test_groups:
# Template_in_group = None # Template_in_group = None
if lab_test_group.lab_test_template: if lab_test_group.lab_test_template:
template_in_group = frappe.get_doc('Lab Test Template', template_in_group = frappe.get_doc('Lab Test Template', lab_test_group.lab_test_template)
lab_test_group.lab_test_template)
if template_in_group: if template_in_group:
if template_in_group.lab_test_template_type == 'Single': if template_in_group.lab_test_template_type == 'Single':
create_normals(template_in_group, lab_test) create_normals(template_in_group, lab_test)
elif template_in_group.lab_test_template_type == 'Compound': elif template_in_group.lab_test_template_type == 'Compound':
normal_heading = lab_test.append('normal_test_items') normal_heading = lab_test.append('normal_test_items')
normal_heading.lab_test_name = template_in_group.lab_test_name normal_heading.lab_test_name = template_in_group.lab_test_name
@ -275,6 +295,7 @@ def load_result_format(lab_test, template, prescription, invoice):
normal_heading.allow_blank = 1 normal_heading.allow_blank = 1
normal_heading.template = template_in_group.name normal_heading.template = template_in_group.name
create_compounds(template_in_group, lab_test, True) create_compounds(template_in_group, lab_test, True)
elif template_in_group.lab_test_template_type == 'Descriptive': elif template_in_group.lab_test_template_type == 'Descriptive':
descriptive_heading = lab_test.append('descriptive_test_items') descriptive_heading = lab_test.append('descriptive_test_items')
descriptive_heading.lab_test_name = template_in_group.lab_test_name descriptive_heading.lab_test_name = template_in_group.lab_test_name
@ -282,6 +303,7 @@ def load_result_format(lab_test, template, prescription, invoice):
descriptive_heading.allow_blank = 1 descriptive_heading.allow_blank = 1
descriptive_heading.template = template_in_group.name descriptive_heading.template = template_in_group.name
create_descriptives(template_in_group, lab_test) create_descriptives(template_in_group, lab_test)
else: # Lab Test Group - Add New Line else: # Lab Test Group - Add New Line
normal = lab_test.append('normal_test_items') normal = lab_test.append('normal_test_items')
normal.lab_test_name = lab_test_group.group_event normal.lab_test_name = lab_test_group.group_event
@ -292,6 +314,7 @@ def load_result_format(lab_test, template, prescription, invoice):
normal.allow_blank = lab_test_group.allow_blank normal.allow_blank = lab_test_group.allow_blank
normal.require_result_value = 1 normal.require_result_value = 1
normal.template = template.name normal.template = template.name
if template.lab_test_template_type != 'No Result': if template.lab_test_template_type != 'No Result':
if prescription: if prescription:
lab_test.prescription = prescription lab_test.prescription = prescription
@ -302,9 +325,10 @@ def load_result_format(lab_test, template, prescription, invoice):
@frappe.whitelist() @frappe.whitelist()
def get_employee_by_user_id(user_id): def get_employee_by_user_id(user_id):
emp_id = frappe.db.get_value('Employee', { 'user_id': user_id }) emp_id = frappe.db.exists('Employee', { 'user_id': user_id })
employee = frappe.get_doc('Employee', emp_id) if emp_id:
return employee return frappe.get_doc('Employee', emp_id)
return None
def insert_lab_test_to_medical_record(doc): def insert_lab_test_to_medical_record(doc):
table_row = False table_row = False
@ -325,7 +349,7 @@ def insert_lab_test_to_medical_record(doc):
table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value
if item.normal_range: if item.normal_range:
table_row += ' ' + _('Normal Range:') + item.normal_range table_row += ' ' + _('Normal Range: ') + item.normal_range
table_row += ' ' + comment table_row += ' ' + comment
elif doc.descriptive_test_items: elif doc.descriptive_test_items:
@ -356,7 +380,7 @@ def insert_lab_test_to_medical_record(doc):
medical_record.save(ignore_permissions = True) medical_record.save(ignore_permissions = True)
def delete_lab_test_from_medical_record(self): def delete_lab_test_from_medical_record(self):
medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name= %s', (self.name)) medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name=%s', (self.name))
if medical_record_id and medical_record_id[0][0]: if medical_record_id and medical_record_id[0][0]:
frappe.delete_doc('Patient Medical Record', medical_record_id[0][0]) frappe.delete_doc('Patient Medical Record', medical_record_id[0][0])

View File

@ -3,13 +3,16 @@
*/ */
frappe.listview_settings['Lab Test'] = { frappe.listview_settings['Lab Test'] = {
add_fields: ['name', 'status', 'invoiced'], add_fields: ['name', 'status', 'invoiced'],
filters: [['docstatus', '=', '0']], filters: [['docstatus', '=', '1']],
get_indicator: function (doc) { get_indicator: function (doc) {
if (doc.status == 'Approved') { if (doc.status === 'Approved') {
return [__('Approved'), 'green', 'status, = ,Approved']; return [__('Approved'), 'green', 'status, =, Approved'];
} } else if (doc.status === 'Rejected') {
if (doc.status == 'Rejected') {
return [__('Rejected'), 'orange', 'status, =, Rejected']; return [__('Rejected'), 'orange', 'status, =, Rejected'];
} else if (doc.status === 'Completed') {
return [__('Completed'), 'green', 'status, =, Completed'];
} else if (doc.status === 'Cancelled') {
return [__('Cancelled'), 'red', 'status, =, Cancelled'];
} }
}, },
onload: function (listview) { onload: function (listview) {
@ -21,7 +24,7 @@ frappe.listview_settings['Lab Test'] = {
var create_multiple_dialog = function (listview) { var create_multiple_dialog = function (listview) {
var dialog = new frappe.ui.Dialog({ var dialog = new frappe.ui.Dialog({
title: 'Create Multiple Lab Test', title: 'Create Multiple Lab Tests',
width: 100, width: 100,
fields: [ fields: [
{ fieldtype: 'Link', label: 'Patient', fieldname: 'patient', options: 'Patient', reqd: 1 }, { fieldtype: 'Link', label: 'Patient', fieldname: 'patient', options: 'Patient', reqd: 1 },
@ -41,7 +44,7 @@ var create_multiple_dialog = function (listview) {
} }
} }
], ],
primary_action_label: __('Create Lab Test'), primary_action_label: __('Create'),
primary_action: function () { primary_action: function () {
frappe.call({ frappe.call({
method: 'erpnext.healthcare.doctype.lab_test.lab_test.create_multiple', method: 'erpnext.healthcare.doctype.lab_test.lab_test.create_multiple',

View File

@ -3,8 +3,204 @@
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import frappe
# test_records = frappe.get_test_records('Lab Test') from frappe.utils import getdate, nowtime
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient
from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
from erpnext.healthcare.doctype.patient_medical_record.test_patient_medical_record import create_lab_test_template as create_blood_test_template
class TestLabTest(unittest.TestCase): class TestLabTest(unittest.TestCase):
pass def test_lab_test_item(self):
lab_template = create_lab_test_template()
self.assertTrue(frappe.db.exists('Item', lab_template.item))
self.assertEqual(frappe.db.get_value('Item Price', {'item_code':lab_template.item}, 'price_list_rate'), lab_template.lab_test_rate)
lab_template.disabled = 1
lab_template.save()
self.assertEquals(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1)
lab_template.reload()
lab_template.disabled = 0
lab_template.save()
def test_descriptive_lab_test(self):
lab_template = create_lab_test_template()
# blank result value not allowed as per template
lab_test = create_lab_test(lab_template)
lab_test.descriptive_test_items[0].result_value = 12
lab_test.descriptive_test_items[2].result_value = 1
lab_test.save()
self.assertRaises(frappe.ValidationError, lab_test.submit)
def test_sample_collection(self):
frappe.db.set_value('Healthcare Settings', 'Healthcare Settings', 'create_sample_collection_for_lab_test', 1)
lab_template = create_lab_test_template()
lab_test = create_lab_test(lab_template)
lab_test.descriptive_test_items[0].result_value = 12
lab_test.descriptive_test_items[1].result_value = 1
lab_test.descriptive_test_items[2].result_value = 2.3
lab_test.save()
# check sample collection created
self.assertTrue(frappe.db.exists('Sample Collection', {'sample': lab_template.sample}))
frappe.db.set_value('Healthcare Settings', 'Healthcare Settings', 'create_sample_collection_for_lab_test', 0)
lab_test = create_lab_test(lab_template)
lab_test.descriptive_test_items[0].result_value = 12
lab_test.descriptive_test_items[1].result_value = 1
lab_test.descriptive_test_items[2].result_value = 2.3
lab_test.save()
# sample collection should not be created
lab_test.reload()
self.assertEquals(lab_test.sample, None)
def test_create_lab_tests_from_sales_invoice(self):
sales_invoice = create_sales_invoice()
create_multiple('Sales Invoice', sales_invoice.name)
sales_invoice.reload()
self.assertIsNotNone(sales_invoice.items[0].reference_dn)
self.assertIsNotNone(sales_invoice.items[1].reference_dn)
def test_create_lab_tests_from_patient_encounter(self):
patient_encounter = create_patient_encounter()
create_multiple('Patient Encounter', patient_encounter.name)
patient_encounter.reload()
self.assertTrue(patient_encounter.lab_test_prescription[0].lab_test_created)
self.assertTrue(patient_encounter.lab_test_prescription[0].lab_test_created)
def create_lab_test_template(test_sensitivity=0, sample_collection=1):
medical_department = create_medical_department()
if frappe.db.exists('Lab Test Template', 'Insulin Resistance'):
return frappe.get_doc('Lab Test Template', 'Insulin Resistance')
template = frappe.new_doc('Lab Test Template')
template.lab_test_name = 'Insulin Resistance'
template.lab_test_template_type = 'Descriptive'
template.lab_test_code = 'Insulin Resistance'
template.lab_test_group = 'Services'
template.department = medical_department
template.is_billable = 1
template.lab_test_description = 'Insulin Resistance'
template.lab_test_rate = 2000
for entry in ['FBS', 'Insulin', 'IR']:
template.append('descriptive_test_templates', {
'particulars': entry,
'allow_blank': 1 if entry=='IR' else 0
})
if test_sensitivity:
template.sensitivity = 1
if sample_collection:
template.sample = create_lab_test_sample()
template.sample_qty = 5.0
template.save()
return template
def create_medical_department():
medical_department = frappe.db.exists('Medical Department', '_Test Medical Department')
if not medical_department:
medical_department = frappe.new_doc('Medical Department')
medical_department.department = '_Test Medical Department'
medical_department.save()
medical_department = medical_department.name
return medical_department
def create_lab_test(lab_template):
patient = create_patient()
lab_test = frappe.new_doc('Lab Test')
lab_test.template = lab_template.name
lab_test.patient = patient
lab_test.patient_sex = 'Female'
lab_test.save()
return lab_test
def create_lab_test_sample():
blood_sample = frappe.db.exists('Lab Test Sample', 'Blood Sample')
if blood_sample:
return blood_sample
sample = frappe.new_doc('Lab Test Sample')
sample.sample = 'Blood Sample'
sample.sample_uom = 'U/ml'
sample.save()
return sample.name
def create_sales_invoice():
patient = create_patient()
medical_department = create_medical_department()
insulin_resistance_template = create_lab_test_template()
blood_test_template = create_blood_test_template(medical_department)
sales_invoice = frappe.new_doc('Sales Invoice')
sales_invoice.patient = patient
sales_invoice.customer = frappe.db.get_value('Patient', patient, 'customer')
sales_invoice.due_date = getdate()
sales_invoice.company = '_Test Company'
sales_invoice.debit_to = get_receivable_account('_Test Company')
tests = [insulin_resistance_template, blood_test_template]
for entry in tests:
sales_invoice.append('items', {
'item_code': entry.item,
'item_name': entry.lab_test_name,
'description': entry.lab_test_description,
'qty': 1,
'uom': 'Nos',
'conversion_factor': 1,
'income_account': get_income_account(None, '_Test Company'),
'rate': entry.lab_test_rate,
'amount': entry.lab_test_rate
})
sales_invoice.set_missing_values()
sales_invoice.submit()
return sales_invoice
def create_patient_encounter():
patient = create_patient()
medical_department = create_medical_department()
insulin_resistance_template = create_lab_test_template()
blood_test_template = create_blood_test_template(medical_department)
patient_encounter = frappe.new_doc('Patient Encounter')
patient_encounter.patient = patient
patient_encounter.practitioner = create_practitioner()
patient_encounter.encounter_date = getdate()
patient_encounter.encounter_time = nowtime()
tests = [insulin_resistance_template, blood_test_template]
for entry in tests:
patient_encounter.append('lab_test_prescription', {
'lab_test_code': entry.item,
'lab_test_name': entry.lab_test_name
})
patient_encounter.submit()
return patient_encounter
def create_practitioner():
practitioner = frappe.db.exists('Healthcare Practitioner', '_Test Healthcare Practitioner')
if not practitioner:
practitioner = frappe.new_doc('Healthcare Practitioner')
practitioner.first_name = '_Test Healthcare Practitioner'
practitioner.gender = 'Female'
practitioner.op_consulting_charge = 500
practitioner.inpatient_visit_charge = 500
practitioner.save(ignore_permissions=True)
practitioner = practitioner.name
return practitioner

View File

@ -93,7 +93,8 @@
"depends_on": "secondary_uom", "depends_on": "secondary_uom",
"fieldname": "conversion_factor", "fieldname": "conversion_factor",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Conversion Factor" "label": "Conversion Factor",
"mandatory_depends_on": "secondary_uom"
}, },
{ {
"default": "0", "default": "0",
@ -106,7 +107,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-06-24 10:59:01.921924", "modified": "2020-07-30 12:36:03.082391",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Lab Test Group Template", "name": "Lab Test Group Template",

View File

@ -34,14 +34,15 @@
"descriptive_test_templates", "descriptive_test_templates",
"section_break_group", "section_break_group",
"lab_test_groups", "lab_test_groups",
"medical_coding_section",
"medical_code_standard",
"medical_code",
"sb_sample_collection", "sb_sample_collection",
"sample", "sample",
"sample_uom", "sample_uom",
"sample_qty", "sample_qty",
"column_break_33",
"sample_details", "sample_details",
"medical_coding_section",
"medical_code",
"medical_code_standard",
"worksheet_section", "worksheet_section",
"worksheet_instructions", "worksheet_instructions",
"result_legend_section", "result_legend_section",
@ -112,7 +113,7 @@
{ {
"default": "1", "default": "1",
"depends_on": "eval:doc.lab_test_template_type != 'Grouped'", "depends_on": "eval:doc.lab_test_template_type != 'Grouped'",
"description": "If unchecked, the item wont be appear in Sales Invoice, but can be used in group test creation. ", "description": "If unchecked, the item will not be available in Sales Invoices for billing but can be used in group test creation. ",
"fieldname": "is_billable", "fieldname": "is_billable",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Billable", "label": "Is Billable",
@ -128,6 +129,7 @@
"mandatory_depends_on": "eval:doc.is_billable == 1" "mandatory_depends_on": "eval:doc.is_billable == 1"
}, },
{ {
"collapsible": 1,
"fieldname": "medical_coding_section", "fieldname": "medical_coding_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Medical Coding" "label": "Medical Coding"
@ -184,7 +186,7 @@
"depends_on": "eval:doc.lab_test_template_type == 'Descriptive'", "depends_on": "eval:doc.lab_test_template_type == 'Descriptive'",
"fieldname": "section_break_special", "fieldname": "section_break_special",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Descriptive" "label": "Descriptive Test"
}, },
{ {
"default": "0", "default": "0",
@ -196,7 +198,7 @@
"depends_on": "eval:doc.lab_test_template_type == 'Grouped'", "depends_on": "eval:doc.lab_test_template_type == 'Grouped'",
"fieldname": "section_break_group", "fieldname": "section_break_group",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Group" "label": "Group Tests"
}, },
{ {
"fieldname": "lab_test_groups", "fieldname": "lab_test_groups",
@ -217,7 +219,6 @@
"no_copy": 1 "no_copy": 1
}, },
{ {
"collapsible": 1,
"fieldname": "sb_sample_collection", "fieldname": "sb_sample_collection",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Sample Collection" "label": "Sample Collection"
@ -311,10 +312,14 @@
"fieldname": "descriptive_test_templates", "fieldname": "descriptive_test_templates",
"fieldtype": "Table", "fieldtype": "Table",
"options": "Descriptive Test Template" "options": "Descriptive Test Template"
},
{
"fieldname": "column_break_33",
"fieldtype": "Column Break"
} }
], ],
"links": [], "links": [],
"modified": "2020-07-13 12:57:09.925436", "modified": "2020-07-30 14:32:40.449818",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Lab Test Template", "name": "Lab Test Template",

View File

@ -15,7 +15,8 @@ class LabTestTemplate(Document):
def validate(self): def validate(self):
if self.is_billable and (not self.lab_test_rate or self.lab_test_rate <= 0.0): if self.is_billable and (not self.lab_test_rate or self.lab_test_rate <= 0.0):
frappe.throw(_("Standard Selling Rate should be greater than zero.")) frappe.throw(_('Standard Selling Rate should be greater than zero.'))
self.validate_conversion_factor() self.validate_conversion_factor()
self.enable_disable_item() self.enable_disable_item()
@ -42,7 +43,9 @@ class LabTestTemplate(Document):
# Remove template reference from item and disable item # Remove template reference from item and disable item
if self.item: if self.item:
try: try:
frappe.delete_doc('Item', self.item) item = self.item
self.db_set('item', '')
frappe.delete_doc('Item', item)
except Exception: except Exception:
frappe.throw(_('Not permitted. Please disable the Lab Test Template')) frappe.throw(_('Not permitted. Please disable the Lab Test Template'))
@ -63,26 +66,26 @@ class LabTestTemplate(Document):
'standard_rate': self.lab_test_rate, 'standard_rate': self.lab_test_rate,
'description': self.lab_test_description 'description': self.lab_test_description
}) })
item.save() item.flags.ignore_mandatory = True
item.save(ignore_permissions=True)
def item_price_exists(self): def item_price_exists(self):
item_price = frappe.db.exists({'doctype': 'Item Price', 'item_code': self.lab_test_code}) item_price = frappe.db.exists({'doctype': 'Item Price', 'item_code': self.lab_test_code})
if item_price: if item_price:
return item_price[0][0] return item_price[0][0]
else: return False
return False
def validate_conversion_factor(self): def validate_conversion_factor(self):
if self.lab_test_template_type == "Single" and self.secondary_uom and not self.conversion_factor: if self.lab_test_template_type == 'Single' and self.secondary_uom and not self.conversion_factor:
frappe.throw(_("Conversion Factor is mandatory")) frappe.throw(_('Conversion Factor is mandatory'))
if self.lab_test_template_type == "Compound": if self.lab_test_template_type == 'Compound':
for item in self.normal_test_templates: for item in self.normal_test_templates:
if item.secondary_uom and not item.conversion_factor: if item.secondary_uom and not item.conversion_factor:
frappe.throw(_("Conversion Factor is mandatory")) frappe.throw(_('Row #{0}: Conversion Factor is mandatory').format(item.idx))
if self.lab_test_template_type == "Grouped": if self.lab_test_template_type == 'Grouped':
for group in self.lab_test_groups: for group in self.lab_test_groups:
if group.template_or_new_line == "Add New Line" and group.secondary_uom and not group.conversion_factor: if group.template_or_new_line == 'Add New Line' and group.secondary_uom and not group.conversion_factor:
frappe.throw(_("Conversion Factor is mandatory")) frappe.throw(_('Row #{0}: Conversion Factor is mandatory').format(group.idx))
def create_item_from_template(doc): def create_item_from_template(doc):
@ -101,9 +104,9 @@ def create_item_from_template(doc):
'include_item_in_manufacturing': 0, 'include_item_in_manufacturing': 0,
'show_in_website': 0, 'show_in_website': 0,
'is_pro_applicable': 0, 'is_pro_applicable': 0,
'disabled': 0 if doc.is_billable and not doc.disabled else doc.disabled, 'disabled': 0 if doc.is_billable and not doc.disabled else doc.disabled,
'stock_uom': uom 'stock_uom': uom
}).insert(ignore_permissions = True, ignore_mandatory = True) }).insert(ignore_permissions=True, ignore_mandatory=True)
# Insert item price # Insert item price
if doc.is_billable and doc.lab_test_rate != 0.0: if doc.is_billable and doc.lab_test_rate != 0.0:
@ -123,7 +126,7 @@ def make_item_price(item, price_list_name, item_price):
'price_list': price_list_name, 'price_list': price_list_name,
'item_code': item, 'item_code': item,
'price_list_rate': item_price 'price_list_rate': item_price
}).insert(ignore_permissions = True, ignore_mandatory = True) }).insert(ignore_permissions=True, ignore_mandatory=True)
@frappe.whitelist() @frappe.whitelist()
def change_test_code_from_template(lab_test_code, doc): def change_test_code_from_template(lab_test_code, doc):
@ -132,8 +135,8 @@ def change_test_code_from_template(lab_test_code, doc):
if frappe.db.exists({'doctype': 'Item', 'item_code': lab_test_code}): if frappe.db.exists({'doctype': 'Item', 'item_code': lab_test_code}):
frappe.throw(_('Lab Test Item {0} already exist').format(lab_test_code)) frappe.throw(_('Lab Test Item {0} already exist').format(lab_test_code))
else: else:
rename_doc('Item', doc.name, lab_test_code, ignore_permissions = True) rename_doc('Item', doc.name, lab_test_code, ignore_permissions=True)
frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_code', lab_test_code) frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_code', lab_test_code)
frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_name', lab_test_code) frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_name', lab_test_code)
rename_doc('Lab Test Template', doc.name, lab_test_code, ignore_permissions = True) rename_doc('Lab Test Template', doc.name, lab_test_code, ignore_permissions=True)
return lab_test_code return lab_test_code

View File

@ -0,0 +1,13 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'template',
'transactions': [
{
'label': _('Lab Tests'),
'items': ['Lab Test']
}
]
}

View File

@ -3,5 +3,5 @@
*/ */
frappe.listview_settings['Lab Test Template'] = { frappe.listview_settings['Lab Test Template'] = {
add_fields: ['lab_test_name', 'lab_test_code', 'lab_test_rate'], add_fields: ['lab_test_name', 'lab_test_code', 'lab_test_rate'],
filters: [['disabled', '=', 0]] filters: [['disabled', '=', 'No']]
}; };

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
import json import json
from frappe.utils import getdate, get_time from frappe.utils import getdate, get_time, flt
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe import _ from frappe import _
import datetime import datetime
@ -45,7 +45,7 @@ class PatientAppointment(Document):
def validate_overlaps(self): def validate_overlaps(self):
end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) \ end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) \
+ datetime.timedelta(minutes=float(self.duration)) + datetime.timedelta(minutes=flt(self.duration))
overlaps = frappe.db.sql(""" overlaps = frappe.db.sql("""
select select

View File

@ -34,7 +34,7 @@ class TestPatientMedicalRecord(unittest.TestCase):
self.assertTrue(medical_rec) self.assertTrue(medical_rec)
template = create_lab_test_template(medical_department) template = create_lab_test_template(medical_department)
lab_test = create_lab_test(template, patient) lab_test = create_lab_test(template.name, patient)
# check for lab test # check for lab test
medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': lab_test.name}) medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': lab_test.name})
self.assertTrue(medical_rec) self.assertTrue(medical_rec)
@ -66,7 +66,7 @@ def create_vital_signs(appointment):
def create_lab_test_template(medical_department): def create_lab_test_template(medical_department):
if frappe.db.exists('Lab Test Template', 'Blood Test'): if frappe.db.exists('Lab Test Template', 'Blood Test'):
return 'Blood Test' return frappe.get_doc('Lab Test Template', 'Blood Test')
template = frappe.new_doc('Lab Test Template') template = frappe.new_doc('Lab Test Template')
template.lab_test_name = 'Blood Test' template.lab_test_name = 'Blood Test'
@ -76,7 +76,7 @@ def create_lab_test_template(medical_department):
template.is_billable = 1 template.is_billable = 1
template.lab_test_rate = 2000 template.lab_test_rate = 2000
template.save() template.save()
return template.name return template
def create_lab_test(template, patient): def create_lab_test(template, patient):
lab_test = frappe.new_doc('Lab Test') lab_test = frappe.new_doc('Lab Test')

View File

@ -44,11 +44,11 @@
} }
], ],
"links": [], "links": [],
"modified": "2020-01-31 12:21:45.975488", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Practitioner Schedule", "name": "Practitioner Schedule",
"owner": "rmehta@gmail.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"create": 1, "create": 1,

View File

@ -3,29 +3,29 @@
frappe.ui.form.on('Sample Collection', { frappe.ui.form.on('Sample Collection', {
refresh: function(frm) { refresh: function(frm) {
if(frappe.defaults.get_default("create_sample_collection_for_lab_test")){ if (frappe.defaults.get_default('create_sample_collection_for_lab_test')) {
frm.add_custom_button(__("View Lab Tests"), function() { frm.add_custom_button(__('View Lab Tests'), function() {
frappe.route_options = {"sample": frm.doc.name}; frappe.route_options = {'sample': frm.doc.name};
frappe.set_route("List", "Lab Test"); frappe.set_route('List', 'Lab Test');
}); });
} }
} }
}); });
frappe.ui.form.on("Sample Collection", "patient", function(frm) { frappe.ui.form.on('Sample Collection', 'patient', function(frm) {
if(frm.doc.patient){ if(frm.doc.patient){
frappe.call({ frappe.call({
"method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail", 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
args: { args: {
patient: frm.doc.patient patient: frm.doc.patient
}, },
callback: function (data) { callback: function (data) {
var age = null; var age = null;
if(data.message.dob){ if (data.message.dob){
age = calculate_age(data.message.dob); age = calculate_age(data.message.dob);
} }
frappe.model.set_value(frm.doctype,frm.docname, "patient_age", age); frappe.model.set_value(frm.doctype,frm.docname, 'patient_age', age);
frappe.model.set_value(frm.doctype,frm.docname, "patient_sex", data.message.sex); frappe.model.set_value(frm.doctype,frm.docname, 'patient_sex', data.message.sex);
} }
}); });
} }
@ -36,5 +36,5 @@ var calculate_age = function(birth) {
var age = new Date(); var age = new Date();
age.setTime(ageMS); age.setTime(ageMS);
var years = age.getFullYear() - 1970; var years = age.getFullYear() - 1970;
return years + " Year(s) " + age.getMonth() + " Month(s) " + age.getDate() + " Day(s)"; return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)';
}; };

View File

@ -9,8 +9,10 @@
"document_type": "Document", "document_type": "Document",
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"patient_details_section",
"naming_series", "naming_series",
"patient", "patient",
"patient_name",
"patient_age", "patient_age",
"patient_sex", "patient_sex",
"column_break_4", "column_break_4",
@ -25,15 +27,17 @@
"collected_by", "collected_by",
"collected_time", "collected_time",
"num_print", "num_print",
"amended_from",
"section_break_15", "section_break_15",
"sample_details" "sample_details",
"amended_from"
], ],
"fields": [ "fields": [
{ {
"fetch_from": "patient.inpatient_record", "fetch_from": "patient.inpatient_record",
"fieldname": "inpatient_record", "fieldname": "inpatient_record",
"fieldtype": "Link", "fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Inpatient Record", "label": "Inpatient Record",
"options": "Inpatient Record", "options": "Inpatient Record",
"read_only": 1 "read_only": 1
@ -42,6 +46,8 @@
"bold": 1, "bold": 1,
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"hide_days": 1,
"hide_seconds": 1,
"label": "Series", "label": "Series",
"no_copy": 1, "no_copy": 1,
"options": "HLC-SC-.YYYY.-", "options": "HLC-SC-.YYYY.-",
@ -52,6 +58,8 @@
"default": "0", "default": "0",
"fieldname": "invoiced", "fieldname": "invoiced",
"fieldtype": "Check", "fieldtype": "Check",
"hide_days": 1,
"hide_seconds": 1,
"label": "Invoiced", "label": "Invoiced",
"no_copy": 1, "no_copy": 1,
"read_only": 1, "read_only": 1,
@ -61,41 +69,60 @@
"fetch_from": "inpatient_record.patient", "fetch_from": "inpatient_record.patient",
"fieldname": "patient", "fieldname": "patient",
"fieldtype": "Link", "fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Patient", "label": "Patient",
"options": "Patient", "options": "Patient",
"reqd": 1,
"search_index": 1 "search_index": 1
}, },
{ {
"fieldname": "column_break_4", "fieldname": "column_break_4",
"fieldtype": "Column Break" "fieldtype": "Column Break",
"hide_days": 1,
"hide_seconds": 1
}, },
{ {
"fieldname": "patient_age", "fieldname": "patient_age",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Age" "hide_days": 1,
"hide_seconds": 1,
"label": "Age",
"read_only": 1
}, },
{ {
"fetch_from": "patient.sex", "fetch_from": "patient.sex",
"fieldname": "patient_sex", "fieldname": "patient_sex",
"fieldtype": "Data", "fieldtype": "Link",
"label": "Gender" "hide_days": 1,
"hide_seconds": 1,
"label": "Gender",
"options": "Gender",
"read_only": 1
}, },
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Company", "label": "Company",
"options": "Company" "options": "Company"
}, },
{ {
"fieldname": "section_break_6", "fieldname": "section_break_6",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Sample Details"
}, },
{ {
"fieldname": "sample", "fieldname": "sample",
"fieldtype": "Link", "fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
@ -108,16 +135,23 @@
"fetch_from": "sample.sample_uom", "fetch_from": "sample.sample_uom",
"fieldname": "sample_uom", "fieldname": "sample_uom",
"fieldtype": "Data", "fieldtype": "Data",
"hide_days": 1,
"hide_seconds": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "UOM" "label": "UOM",
"read_only": 1
}, },
{ {
"fieldname": "column_break_10", "fieldname": "column_break_10",
"fieldtype": "Column Break" "fieldtype": "Column Break",
"hide_days": 1,
"hide_seconds": 1
}, },
{ {
"fieldname": "collected_by", "fieldname": "collected_by",
"fieldtype": "Link", "fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Collected By", "label": "Collected By",
"options": "User" "options": "User"
@ -125,20 +159,27 @@
{ {
"fieldname": "collected_time", "fieldname": "collected_time",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"label": "Collected Time" "hide_days": 1,
"hide_seconds": 1,
"label": "Collected On"
}, },
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"default": "1", "default": "1",
"description": "Number of prints required for labelling the samples",
"fieldname": "num_print", "fieldname": "num_print",
"fieldtype": "Int", "fieldtype": "Int",
"label": "No. of print", "hide_days": 1,
"hide_seconds": 1,
"label": "No. of prints",
"print_hide": 1, "print_hide": 1,
"report_hide": 1 "report_hide": 1
}, },
{ {
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "fieldtype": "Link",
"hide_days": 1,
"hide_seconds": 1,
"label": "Amended From", "label": "Amended From",
"no_copy": 1, "no_copy": 1,
"options": "Sample Collection", "options": "Sample Collection",
@ -147,25 +188,43 @@
}, },
{ {
"fieldname": "section_break_15", "fieldname": "section_break_15",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "sample_qty", "fieldname": "sample_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hide_days": 1,
"hide_seconds": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Quantity" "label": "Quantity"
}, },
{ {
"fieldname": "sample_details", "fieldname": "sample_details",
"fieldtype": "Long Text", "fieldtype": "Long Text",
"hide_days": 1,
"hide_seconds": 1,
"ignore_xss_filter": 1, "ignore_xss_filter": 1,
"label": "Collection Details" "label": "Collection Details"
},
{
"fieldname": "patient_details_section",
"fieldtype": "Section Break",
"label": "Patient Details"
},
{
"fetch_from": "patient.patient_name",
"fieldname": "patient_name",
"fieldtype": "Data",
"label": "Patient Name",
"read_only": 1
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-25 14:36:46.990469", "modified": "2020-07-30 16:53:13.076104",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Sample Collection", "name": "Sample Collection",

View File

@ -3,7 +3,12 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import flt
from frappe import _
class SampleCollection(Document): class SampleCollection(Document):
pass def validate(self):
if flt(self.sample_qty) <= 0:
frappe.throw(_('Sample Quantity cannot be negative or 0'), title=_('Invalid Quantity'))

View File

@ -4,29 +4,54 @@
frappe.query_reports["Lab Test Report"] = { frappe.query_reports["Lab Test Report"] = {
"filters": [ "filters": [
{ {
"fieldname":"from_date", "fieldname": "from_date",
"label": __("From Date"), "label": __("From Date"),
"fieldtype": "Date", "fieldtype": "Date",
"default": frappe.datetime.now_date(), "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
"width": "80" "reqd": 1
}, },
{ {
"fieldname":"to_date", "fieldname": "to_date",
"label": __("To Date"), "label": __("To Date"),
"fieldtype": "Date", "fieldtype": "Date",
"default": frappe.datetime.now_date() "default": frappe.datetime.now_date(),
"reqd": 1
}, },
{ {
"fieldname":"patient", "fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"default": frappe.defaults.get_default("Company"),
"options": "Company"
},
{
"fieldname": "template",
"label": __("Lab Test Template"),
"fieldtype": "Link",
"options": "Lab Test Template"
},
{
"fieldname": "patient",
"label": __("Patient"), "label": __("Patient"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Patient" "options": "Patient"
}, },
{ {
"fieldname":"department", "fieldname": "department",
"label": __("Medical Department"), "label": __("Medical Department"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Medical Department" "options": "Medical Department"
},
{
"fieldname": "status",
"label": __("Status"),
"fieldtype": "Select",
"options": "\nCompleted\nApproved\nRejected"
},
{
"fieldname": "invoiced",
"label": __("Invoiced"),
"fieldtype": "Check"
} }
] ]
}; };

View File

@ -1,30 +1,31 @@
{ {
"add_total_row": 1, "add_total_row": 0,
"creation": "2013-04-23 18:15:29", "creation": "2013-04-23 18:15:29",
"disabled": 0, "disable_prepared_report": 0,
"docstatus": 0, "disabled": 0,
"doctype": "Report", "docstatus": 0,
"idx": 1, "doctype": "Report",
"is_standard": "Yes", "idx": 1,
"modified": "2018-08-06 11:41:50.218737", "is_standard": "Yes",
"modified_by": "Administrator", "modified": "2020-07-30 18:53:20.102873",
"module": "Healthcare", "modified_by": "Administrator",
"name": "Lab Test Report", "module": "Healthcare",
"owner": "Administrator", "name": "Lab Test Report",
"prepared_report": 0, "owner": "Administrator",
"ref_doctype": "Lab Test", "prepared_report": 0,
"report_name": "Lab Test Report", "ref_doctype": "Lab Test",
"report_type": "Script Report", "report_name": "Lab Test Report",
"report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "Laboratory User" "role": "Laboratory User"
}, },
{ {
"role": "Nursing User" "role": "Nursing User"
}, },
{ {
"role": "LabTest Approver" "role": "LabTest Approver"
}, },
{ {
"role": "Healthcare Administrator" "role": "Healthcare Administrator"
} }

View File

@ -8,51 +8,204 @@ from frappe import msgprint, _
def execute(filters=None): def execute(filters=None):
if not filters: filters = {} if not filters: filters = {}
lab_test_list = get_lab_test(filters) data, columns = [], []
columns = get_columns() columns = get_columns()
lab_test_list = get_lab_tests(filters)
if not lab_test_list: if not lab_test_list:
msgprint(_("No record found")) msgprint(_('No records found'))
return columns, lab_test_list return columns, lab_test_list
data = [] data = []
for lab_test in lab_test_list: for lab_test in lab_test_list:
row = [ lab_test.lab_test_name, lab_test.patient, lab_test.practitioner, lab_test.invoiced, lab_test.status, lab_test.result_date, lab_test.department] row = frappe._dict({
'test': lab_test.name,
'template': lab_test.template,
'company': lab_test.company,
'patient': lab_test.patient,
'patient_name': lab_test.patient_name,
'practitioner': lab_test.practitioner,
'employee': lab_test.employee,
'status': lab_test.status,
'invoiced': lab_test.invoiced,
'result_date': lab_test.result_date,
'department': lab_test.department
})
data.append(row) data.append(row)
return columns, data chart = get_chart_data(data)
report_summary = get_report_summary(data)
return columns, data, None, chart, report_summary
def get_columns(): def get_columns():
columns = [ return [
_("Test") + ":Data:120", {
_("Patient") + ":Link/Patient:180", 'fieldname': 'test',
_("Healthcare Practitioner") + ":Link/Healthcare Practitioner:120", 'label': _('Lab Test'),
_("Invoiced") + ":Check:100", 'fieldtype': 'Link',
_("Status") + ":Data:120", 'options': 'Lab Test',
_("Result Date") + ":Date:120", 'width': '120'
_("Department") + ":Data:120", },
{
'fieldname': 'template',
'label': _('Lab Test Template'),
'fieldtype': 'Link',
'options': 'Lab Test Template',
'width': '120'
},
{
'fieldname': 'company',
'label': _('Company'),
'fieldtype': 'Link',
'options': 'Company',
'width': '120'
},
{
'fieldname': 'patient',
'label': _('Patient'),
'fieldtype': 'Link',
'options': 'Patient',
'width': '120'
},
{
'fieldname': 'patient_name',
'label': _('Patient Name'),
'fieldtype': 'Data',
'width': '120'
},
{
'fieldname': 'employee',
'label': _('Lab Technician'),
'fieldtype': 'Link',
'options': 'Employee',
'width': '120'
},
{
'fieldname': 'status',
'label': _('Status'),
'fieldtype': 'Data',
'width': '100'
},
{
'fieldname': 'invoiced',
'label': _('Invoiced'),
'fieldtype': 'Check',
'width': '100'
},
{
'fieldname': 'result_date',
'label': _('Result Date'),
'fieldtype': 'Date',
'width': '100'
},
{
'fieldname': 'practitioner',
'label': _('Requesting Practitioner'),
'fieldtype': 'Link',
'options': 'Healthcare Practitioner',
'width': '120'
},
{
'fieldname': 'department',
'label': _('Medical Department'),
'fieldtype': 'Link',
'options': 'Medical Department',
'width': '100'
}
] ]
return columns def get_lab_tests(filters):
conditions = get_conditions(filters)
data = frappe.get_all(
doctype='Lab Test',
fields=['name', 'template', 'company', 'patient', 'patient_name', 'practitioner', 'employee', 'status', 'invoiced', 'result_date', 'department'],
filters=conditions,
order_by='submitted_date desc'
)
return data
def get_conditions(filters): def get_conditions(filters):
conditions = "" conditions = {
'docstatus': ('=', 1)
}
if filters.get("patient"): if filters.get('from_date') and filters.get('to_date'):
conditions += "and patient = %(patient)s" conditions['result_date'] = ('between', (filters.get('from_date'), filters.get('to_date')))
if filters.get("from_date"): filters.pop('from_date')
conditions += "and result_date >= %(from_date)s" filters.pop('to_date')
if filters.get("to_date"):
conditions += " and result_date <= %(to_date)s" for key, value in filters.items():
if filters.get("department"): if filters.get(key):
conditions += " and department = %(department)s" conditions[key] = value
return conditions return conditions
def get_lab_test(filters): def get_chart_data(data):
conditions = get_conditions(filters) if not data:
return frappe.db.sql("""select name, patient, lab_test_name, patient_name, status, result_date, practitioner, invoiced, department return None
from `tabLab Test`
where docstatus<2 %s order by submitted_date desc, name desc""" % labels = ['Completed', 'Approved', 'Rejected']
conditions, filters, as_dict=1)
status_wise_data = {
'Completed': 0,
'Approved': 0,
'Rejected': 0
}
datasets = []
for entry in data:
status_wise_data[entry.status] += 1
datasets.append({
'name': 'Lab Test Status',
'values': [status_wise_data.get('Completed'), status_wise_data.get('Approved'), status_wise_data.get('Rejected')]
})
chart = {
'data': {
'labels': labels,
'datasets': datasets
},
'type': 'donut',
'height': 300,
}
return chart
def get_report_summary(data):
if not data:
return None
total_lab_tests = len(data)
invoiced_lab_tests, unbilled_lab_tests = 0, 0
for entry in data:
if entry.invoiced:
invoiced_lab_tests += 1
else:
unbilled_lab_tests += 1
return [
{
'value': total_lab_tests,
'indicator': 'Blue',
'label': 'Total Lab Tests',
'datatype': 'Int',
},
{
'value': invoiced_lab_tests,
'indicator': 'Green',
'label': 'Invoiced Lab Tests',
'datatype': 'Int',
},
{
'value': unbilled_lab_tests,
'indicator': 'Red',
'label': 'Unbilled Lab Tests',
'datatype': 'Int',
}
]

View File

@ -282,6 +282,11 @@ auto_cancel_exempted_doctypes= [
] ]
scheduler_events = { scheduler_events = {
"cron": {
"0/30 * * * *": [
"erpnext.utilities.doctype.video.video.update_youtube_data",
]
},
"all": [ "all": [
"erpnext.projects.doctype.project.project.project_status_update_reminder", "erpnext.projects.doctype.project.project.project_status_update_reminder",
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder", "erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder",

View File

@ -696,11 +696,11 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-01-30 11:28:08.401459", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Appraisal", "name": "Appraisal",
"owner": "ashwini@webnotestech.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,

View File

@ -207,11 +207,11 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-07-11 03:27:57.897071", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Appraisal Goal", "name": "Appraisal Goal",
"owner": "ashwini@webnotestech.com", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 0, "quick_entry": 0,
"read_only": 0, "read_only": 0,

View File

@ -113,11 +113,11 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-12-13 12:37:56.937023", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Appraisal Template", "name": "Appraisal Template",
"owner": "ashwini@webnotestech.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,

View File

@ -78,11 +78,11 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-07-11 03:27:57.979215", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Appraisal Template Goal", "name": "Appraisal Template Goal",
"owner": "ashwini@webnotestech.com", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 0, "quick_entry": 0,
"read_only": 0, "read_only": 0,

View File

@ -205,11 +205,11 @@
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-29 13:51:37.177231", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Attendance", "name": "Attendance",
"owner": "ashwini@webnotestech.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"cancel": 1, "cancel": 1,

View File

@ -371,12 +371,12 @@
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-06-15 12:43:04.099803", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Expense Claim", "name": "Expense Claim",
"name_case": "Title Case", "name_case": "Title Case",
"owner": "harshada@webnotestech.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1, "amend": 1,

View File

@ -120,11 +120,11 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-05-11 18:54:35.601592", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Expense Claim Detail", "name": "Expense Claim Detail",
"owner": "harshada@webnotestech.com", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC"

View File

@ -48,11 +48,11 @@
"icon": "fa fa-flag", "icon": "fa fa-flag",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2019-12-11 13:38:59.534034", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Expense Claim Type", "name": "Expense Claim Type",
"owner": "harshada@webnotestech.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"create": 1, "create": 1,

View File

@ -229,11 +229,11 @@
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"max_attachments": 1, "max_attachments": 1,
"modified": "2017-11-14 12:51:34.980103", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Upload Attendance", "name": "Upload Attendance",
"owner": "harshada@webnotestech.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,

View File

@ -40,8 +40,8 @@
"mapping_name": "Company to Hub Company", "mapping_name": "Company to Hub Company",
"mapping_type": "Push", "mapping_type": "Push",
"migration_id_field": "hub_sync_id", "migration_id_field": "hub_sync_id",
"modified": "2018-02-14 15:57:05.571142", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "achilles@erpnext.com", "modified_by": "Administrator",
"name": "Company to Hub Company", "name": "Company to Hub Company",
"owner": "Administrator", "owner": "Administrator",
"page_length": 10, "page_length": 10,

View File

@ -21,10 +21,10 @@
"mapping_name": "Hub Message to Lead", "mapping_name": "Hub Message to Lead",
"mapping_type": "Pull", "mapping_type": "Pull",
"migration_id_field": "hub_sync_id", "migration_id_field": "hub_sync_id",
"modified": "2018-02-14 15:57:05.606597", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "achilles@erpnext.com", "modified_by": "Administrator",
"name": "Hub Message to Lead", "name": "Hub Message to Lead",
"owner": "frappetest@gmail.com", "owner": "Administrator",
"page_length": 10, "page_length": 10,
"remote_objectname": "Hub Message", "remote_objectname": "Hub Message",
"remote_primary_key": "name" "remote_primary_key": "name"

View File

@ -121,8 +121,8 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-09-01 13:56:07.816894", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "netchamp@rawcoderz.com", "modified_by": "Administrator",
"module": "Hub Node", "module": "Hub Node",
"name": "Hub User", "name": "Hub User",
"name_case": "", "name_case": "",

View File

@ -54,12 +54,12 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-03-06 04:41:17.916243", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Hub Node", "module": "Hub Node",
"name": "Hub Users", "name": "Hub Users",
"name_case": "", "name_case": "",
"owner": "test1@example.com", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "read_only": 0,

View File

@ -352,12 +352,12 @@
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-02-01 14:21:16.729848", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Hub Node", "module": "Hub Node",
"name": "Marketplace Settings", "name": "Marketplace Settings",
"name_case": "", "name_case": "",
"owner": "netchamp@rawcoderz.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,

View File

@ -813,7 +813,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-08-21 14:44:33.670332", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Maintenance", "module": "Maintenance",
"name": "Maintenance Schedule", "name": "Maintenance Schedule",

View File

@ -1001,11 +1001,11 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2020-07-15 14:44:44.911402", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Maintenance", "module": "Maintenance",
"name": "Maintenance Visit", "name": "Maintenance Visit",
"owner": "ashwini@webnotestech.com", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1, "amend": 1,

View File

@ -125,11 +125,11 @@
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-10-03 14:55:52.786805", "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Maintenance", "module": "Maintenance",
"name": "Maintenance Visit Purpose", "name": "Maintenance Visit Purpose",
"owner": "ashwini@webnotestech.com", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",

View File

@ -1,336 +1,105 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "allow_rename": 1,
"allow_import": 0, "autoname": "field:email",
"allow_rename": 1, "creation": "2017-09-19 16:20:27.510196",
"autoname": "field:email", "doctype": "DocType",
"beta": 0, "editable_grid": 1,
"creation": "2017-09-19 16:20:27.510196", "engine": "InnoDB",
"custom": 0, "field_order": [
"docstatus": 0, "donor_name",
"doctype": "DocType", "column_break_5",
"document_type": "", "donor_type",
"editable_grid": 1, "email",
"engine": "InnoDB", "image",
"address_contacts",
"address_html",
"column_break_9",
"contact_html"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "donor_name",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Donor Name",
"columns": 0, "reqd": 1
"fieldname": "donor_name", },
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Donor Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_5",
"allow_on_submit": 0, "fieldtype": "Column Break"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "donor_type",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Donor Type",
"columns": 0, "options": "Donor Type",
"fieldname": "donor_type", "reqd": 1
"fieldtype": "Link", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Donor Type",
"length": 0,
"no_copy": 0,
"options": "Donor Type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "email",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Email",
"columns": 0, "reqd": 1,
"fieldname": "email", "unique": 1
"fieldtype": "Data", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "image",
"allow_on_submit": 0, "fieldtype": "Attach Image",
"bold": 0, "hidden": 1,
"collapsible": 0, "label": "Image",
"columns": 0, "no_copy": 1,
"fieldname": "image", "print_hide": 1
"fieldtype": "Attach Image", },
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:!doc.__islocal;",
"allow_on_submit": 0, "fieldname": "address_contacts",
"bold": 0, "fieldtype": "Section Break",
"collapsible": 0, "label": "Address and Contact",
"columns": 0, "options": "fa fa-map-marker"
"fieldname": "address_contacts", },
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address and Contact",
"length": 0,
"no_copy": 0,
"options": "fa fa-map-marker",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "address_html",
"allow_on_submit": 0, "fieldtype": "HTML",
"bold": 0, "label": "Address HTML"
"collapsible": 0, },
"columns": 0,
"fieldname": "address_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_9",
"allow_on_submit": 0, "fieldtype": "Column Break"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "contact_html",
"allow_on_submit": 0, "fieldtype": "HTML",
"bold": 0, "label": "Contact HTML"
"collapsible": 0,
"columns": 0,
"fieldname": "contact_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "image_field": "image",
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2020-09-16 23:46:04.083274",
"idx": 0, "modified_by": "Administrator",
"image_field": "image", "module": "Non Profit",
"image_view": 0, "name": "Donor",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-01-22 15:53:35.059946",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Donor",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "Non Profit Manager",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "restrict_to_domain": "Non Profit",
"read_only_onload": 0, "sort_field": "modified",
"restrict_to_domain": "Non Profit", "sort_order": "DESC",
"show_name_in_global_search": 0, "title_field": "donor_name",
"sort_field": "modified", "track_changes": 1
"sort_order": "DESC",
"title_field": "donor_name",
"track_changes": 1,
"track_seen": 0
} }

View File

@ -111,6 +111,7 @@
"options": "Supplier" "options": "Supplier"
}, },
{ {
"depends_on": "eval:!doc.__islocal;",
"fieldname": "address_contacts", "fieldname": "address_contacts",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Address and Contact", "label": "Address and Contact",
@ -177,7 +178,7 @@
], ],
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"modified": "2020-08-06 10:06:01.153564", "modified": "2020-09-16 23:44:13.596948",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Non Profit", "module": "Non Profit",
"name": "Member", "name": "Member",

View File

@ -90,9 +90,9 @@
}, },
{ {
"fieldname": "currency", "fieldname": "currency",
"fieldtype": "Select", "fieldtype": "Link",
"label": "Currency", "label": "Currency",
"options": "USD\nINR" "options": "Currency"
}, },
{ {
"fieldname": "amount", "fieldname": "amount",
@ -126,7 +126,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-07-31 13:57:02.328995", "modified": "2020-09-19 14:28:11.532696",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Non Profit", "module": "Non Profit",
"name": "Membership", "name": "Membership",
@ -161,4 +161,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1 "track_changes": 1
} }

View File

@ -1,580 +1,148 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "allow_rename": 1,
"allow_guest_to_view": 0, "autoname": "field:email",
"allow_import": 0, "creation": "2017-09-19 16:16:45.676019",
"allow_rename": 1, "doctype": "DocType",
"autoname": "field:email", "editable_grid": 1,
"beta": 0, "engine": "InnoDB",
"creation": "2017-09-19 16:16:45.676019", "field_order": [
"custom": 0, "volunteer_name",
"docstatus": 0, "column_break_5",
"doctype": "DocType", "volunteer_type",
"document_type": "", "email",
"editable_grid": 1, "image",
"engine": "InnoDB", "address_contacts",
"address_html",
"column_break_9",
"contact_html",
"volunteer_availability_and_skills_details",
"availability",
"availability_timeslot",
"column_break_12",
"volunteer_skills",
"section_break_15",
"note"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "volunteer_name",
"allow_in_quick_entry": 0, "fieldtype": "Data",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Volunteer Name",
"collapsible": 0, "reqd": 1
"columns": 0, },
"fieldname": "volunteer_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Volunteer Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_5",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "volunteer_type",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Volunteer Type",
"collapsible": 0, "options": "Volunteer Type",
"columns": 0, "reqd": 1
"fieldname": "volunteer_type", },
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Volunteer Type",
"length": 0,
"no_copy": 0,
"options": "Volunteer Type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "email",
"allow_in_quick_entry": 0, "fieldtype": "Data",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Email",
"collapsible": 0, "reqd": 1,
"columns": 0,
"fieldname": "email",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1 "unique": 1
}, },
{ {
"allow_bulk_edit": 0, "fieldname": "image",
"allow_in_quick_entry": 0, "fieldtype": "Attach Image",
"allow_on_submit": 0, "hidden": 1,
"bold": 0, "label": "Image",
"collapsible": 0, "no_copy": 1,
"columns": 0, "print_hide": 1
"fieldname": "image", },
"fieldtype": "Attach Image",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:!doc.__islocal;",
"allow_in_quick_entry": 0, "fieldname": "address_contacts",
"allow_on_submit": 0, "fieldtype": "Section Break",
"bold": 0, "label": "Address and Contact",
"collapsible": 0, "options": "fa fa-map-marker"
"columns": 0, },
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address and Contact",
"length": 0,
"no_copy": 0,
"options": "fa fa-map-marker",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "address_html",
"allow_in_quick_entry": 0, "fieldtype": "HTML",
"allow_on_submit": 0, "label": "Address HTML"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "address_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_9",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "contact_html",
"allow_in_quick_entry": 0, "fieldtype": "HTML",
"allow_on_submit": 0, "label": "Contact HTML"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "contact_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "volunteer_availability_and_skills_details",
"allow_in_quick_entry": 0, "fieldtype": "Section Break",
"allow_on_submit": 0, "label": "Availability and Skills"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "volunteer_availability_and_skills_details",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Availability and Skills",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "availability",
"allow_in_quick_entry": 0, "fieldtype": "Select",
"allow_on_submit": 0, "label": "Availability",
"bold": 0, "options": "\nWeekly\nWeekdays\nWeekends"
"collapsible": 0, },
"columns": 0,
"fieldname": "availability",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Availability",
"length": 0,
"no_copy": 0,
"options": "\nWeekly\nWeekdays\nWeekends",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "availability_timeslot",
"allow_in_quick_entry": 0, "fieldtype": "Select",
"allow_on_submit": 0, "label": "Availability Timeslot",
"bold": 0, "options": "\nMorning\nAfternoon\nEvening\nAnytime"
"collapsible": 0, },
"columns": 0,
"fieldname": "availability_timeslot",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Availability Timeslot",
"length": 0,
"no_copy": 0,
"options": "\nMorning\nAfternoon\nEvening\nAnytime",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_12",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "volunteer_skills",
"allow_in_quick_entry": 0, "fieldtype": "Table",
"allow_on_submit": 0, "label": "Volunteer Skills",
"bold": 0, "options": "Volunteer Skill"
"collapsible": 0, },
"columns": 0,
"fieldname": "volunteer_skills",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Volunteer Skills",
"length": 0,
"no_copy": 0,
"options": "Volunteer Skill",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "section_break_15",
"allow_in_quick_entry": 0, "fieldtype": "Section Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_15",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "note",
"allow_in_quick_entry": 0, "fieldtype": "Long Text",
"allow_on_submit": 0, "label": "Note"
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "note",
"fieldtype": "Long Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Note",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "image_field": "image",
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2020-09-16 23:45:15.595952",
"idx": 0, "modified_by": "Administrator",
"image_field": "image", "module": "Non Profit",
"image_view": 0, "name": "Volunteer",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-11-04 03:36:25.776211",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Volunteer",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"cancel": 0, "delete": 1,
"create": 1, "email": 1,
"delete": 1, "export": 1,
"email": 1, "print": 1,
"export": 1, "read": 1,
"if_owner": 0, "report": 1,
"import": 0, "role": "Non Profit Manager",
"permlevel": 0, "share": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "restrict_to_domain": "Non Profit",
"read_only_onload": 0, "sort_field": "modified",
"restrict_to_domain": "Non Profit", "sort_order": "DESC",
"show_name_in_global_search": 0, "title_field": "volunteer_name",
"sort_field": "modified", "track_changes": 1
"sort_order": "DESC",
"title_field": "volunteer_name",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
} }

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