Merge branch 'develop' into item-tax-update-items
This commit is contained in:
commit
65c2c4f85f
@ -98,7 +98,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Accounting",
|
||||
"modified": "2020-06-19 12:42:44.054598",
|
||||
"modified": "2020-09-09 11:45:33.766400",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting",
|
||||
@ -147,11 +147,6 @@
|
||||
"link_to": "Trial Balance",
|
||||
"type": "Report"
|
||||
},
|
||||
{
|
||||
"label": "Point of Sale",
|
||||
"link_to": "point-of-sale",
|
||||
"type": "Page"
|
||||
},
|
||||
{
|
||||
"label": "Dashboard",
|
||||
"link_to": "Accounts",
|
||||
|
@ -55,7 +55,7 @@ class BankStatementTransactionEntry(Document):
|
||||
|
||||
def populate_payment_entries(self):
|
||||
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):
|
||||
frappe.throw(_("Transactions already retreived from the statement"))
|
||||
|
||||
@ -65,7 +65,7 @@ class BankStatementTransactionEntry(Document):
|
||||
if self.bank_settings:
|
||||
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
|
||||
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:
|
||||
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"]))
|
||||
@ -398,20 +398,21 @@ def get_transaction_info(headers, header_index, row):
|
||||
transaction[header] = ""
|
||||
return transaction
|
||||
|
||||
def get_transaction_entries(filename, headers):
|
||||
def get_transaction_entries(file_url, headers):
|
||||
header_index = {}
|
||||
rows, transactions = [], []
|
||||
|
||||
if (filename.lower().endswith("xlsx")):
|
||||
if (file_url.lower().endswith("xlsx")):
|
||||
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
|
||||
rows = read_xlsx_file_from_attached_file(file_id=filename)
|
||||
elif (filename.lower().endswith("csv")):
|
||||
rows = read_xlsx_file_from_attached_file(file_url=file_url)
|
||||
elif (file_url.lower().endswith("csv")):
|
||||
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()
|
||||
with open(filepath,'rb') as csvfile:
|
||||
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)
|
||||
else:
|
||||
frappe.throw(_("Only .csv and .xlsx files are supported currently"))
|
||||
|
@ -91,15 +91,11 @@ class TestBankTransaction(unittest.TestCase):
|
||||
self.assertEqual(frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0)
|
||||
self.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None)
|
||||
|
||||
def add_transactions():
|
||||
if frappe.flags.test_bank_transactions_created:
|
||||
return
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
|
||||
try:
|
||||
frappe.get_doc({
|
||||
"doctype": "Bank",
|
||||
"bank_name":"Citi Bank",
|
||||
"bank_name":bank_name,
|
||||
}).insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
@ -108,12 +104,19 @@ def add_transactions():
|
||||
frappe.get_doc({
|
||||
"doctype": "Bank Account",
|
||||
"account_name":"Checking Account",
|
||||
"bank": "Citi Bank",
|
||||
"account": "_Test Bank - _TC"
|
||||
"bank": bank_name,
|
||||
"account": account_name
|
||||
}).insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
def add_transactions():
|
||||
if frappe.flags.test_bank_transactions_created:
|
||||
return
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
create_bank_account()
|
||||
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Bank Transaction",
|
||||
"description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
|
||||
|
@ -9,6 +9,8 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
from frappe.test_runner import make_test_objects
|
||||
|
||||
test_dependencies = ['Item']
|
||||
|
||||
def test_create_test_data():
|
||||
frappe.set_user("Administrator")
|
||||
# create test item
|
||||
@ -95,7 +97,6 @@ def test_create_test_data():
|
||||
})
|
||||
coupon_code.insert()
|
||||
|
||||
|
||||
class TestCouponCode(unittest.TestCase):
|
||||
def setUp(self):
|
||||
test_create_test_data()
|
||||
|
@ -638,20 +638,12 @@ $.extend(erpnext.journal_entry, {
|
||||
return { filters: filters };
|
||||
},
|
||||
|
||||
reverse_journal_entry: function(frm) {
|
||||
var me = frm.doc;
|
||||
for(var i=0; i<me.accounts.length; i++) {
|
||||
me.accounts[i].credit += me.accounts[i].debit;
|
||||
me.accounts[i].debit = me.accounts[i].credit - me.accounts[i].debit;
|
||||
me.accounts[i].credit -= me.accounts[i].debit;
|
||||
me.accounts[i].credit_in_account_currency = me.accounts[i].credit;
|
||||
me.accounts[i].debit_in_account_currency = me.accounts[i].debit;
|
||||
me.accounts[i].reference_type = "Journal Entry";
|
||||
me.accounts[i].reference_name = me.name
|
||||
}
|
||||
frm.copy_doc();
|
||||
cur_frm.reload_doc();
|
||||
}
|
||||
reverse_journal_entry: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",
|
||||
frm: cur_frm
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
$.extend(erpnext.journal_entry, {
|
||||
|
@ -1021,3 +1021,34 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
||||
journal_entry.posting_date = nowdate()
|
||||
journal_entry.inter_company_journal_entry_reference = name
|
||||
return journal_entry.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_reverse_journal_entry(source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def update_accounts(source, target, source_parent):
|
||||
target.reference_type = "Journal Entry"
|
||||
target.reference_name = source_parent.name
|
||||
|
||||
doclist = get_mapped_doc("Journal Entry", source_name, {
|
||||
"Journal Entry": {
|
||||
"doctype": "Journal Entry",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
}
|
||||
},
|
||||
"Journal Entry Account": {
|
||||
"doctype": "Journal Entry Account",
|
||||
"field_map": {
|
||||
"account_currency": "account_currency",
|
||||
"exchange_rate": "exchange_rate",
|
||||
"debit_in_account_currency": "credit_in_account_currency",
|
||||
"debit": "credit",
|
||||
"credit_in_account_currency": "debit_in_account_currency",
|
||||
"credit": "debit",
|
||||
},
|
||||
"postprocess": update_accounts,
|
||||
},
|
||||
}, target_doc)
|
||||
|
||||
return doclist
|
@ -167,6 +167,49 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
self.assertFalse(gle)
|
||||
|
||||
def test_reverse_journal_entry(self):
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||
"Sales - _TC", 100, exchange_rate=50, save=False)
|
||||
|
||||
jv.get("accounts")[1].credit_in_account_currency = 5000
|
||||
jv.get("accounts")[1].exchange_rate = 1
|
||||
jv.submit()
|
||||
|
||||
rjv = make_reverse_journal_entry(jv.name)
|
||||
rjv.posting_date = nowdate()
|
||||
rjv.submit()
|
||||
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
|
||||
debit_in_account_currency, credit_in_account_currency
|
||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
||||
order by account asc""", rjv.name, as_dict=1)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
|
||||
expected_values = {
|
||||
"_Test Bank USD - _TC": {
|
||||
"account_currency": "USD",
|
||||
"debit": 0,
|
||||
"debit_in_account_currency": 0,
|
||||
"credit": 5000,
|
||||
"credit_in_account_currency": 100,
|
||||
},
|
||||
"Sales - _TC": {
|
||||
"account_currency": "INR",
|
||||
"debit": 5000,
|
||||
"debit_in_account_currency": 5000,
|
||||
"credit": 0,
|
||||
"credit_in_account_currency": 0,
|
||||
}
|
||||
}
|
||||
|
||||
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_values[gle.account][field], gle[field])
|
||||
|
||||
def test_disallow_change_in_account_currency_for_a_party(self):
|
||||
# create jv in USD
|
||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||
|
@ -1172,30 +1172,23 @@ def make_payment_order(source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
def set_missing_values(source, target):
|
||||
target.payment_order_type = "Payment Entry"
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.bank_account = source_parent.party_bank_account
|
||||
target_doc.amount = source_doc.allocated_amount
|
||||
target_doc.account = source_parent.paid_to
|
||||
target_doc.payment_entry = source_parent.name
|
||||
target_doc.supplier = source_parent.party
|
||||
target_doc.mode_of_payment = source_parent.mode_of_payment
|
||||
|
||||
target.append('references', dict(
|
||||
reference_doctype="Payment Entry",
|
||||
reference_name=source.name,
|
||||
bank_account=source.party_bank_account,
|
||||
amount=source.paid_amount,
|
||||
account=source.paid_to,
|
||||
supplier=source.party,
|
||||
mode_of_payment=source.mode_of_payment,
|
||||
))
|
||||
|
||||
doclist = get_mapped_doc("Payment Entry", source_name, {
|
||||
"Payment Entry": {
|
||||
"doctype": "Payment Order",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
},
|
||||
}
|
||||
},
|
||||
"Payment Entry Reference": {
|
||||
"doctype": "Payment Order Reference",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
},
|
||||
"postprocess": update_item
|
||||
},
|
||||
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
|
@ -21,10 +21,15 @@ class PaymentOrder(Document):
|
||||
if cancel:
|
||||
status = 'Initiated'
|
||||
|
||||
ref_field = "status" if self.payment_order_type == "Payment Request" else "payment_order_status"
|
||||
if self.payment_order_type == "Payment Request":
|
||||
ref_field = "status"
|
||||
ref_doc_field = frappe.scrub(self.payment_order_type)
|
||||
else:
|
||||
ref_field = "payment_order_status"
|
||||
ref_doc_field = "reference_name"
|
||||
|
||||
for d in self.references:
|
||||
frappe.db.set_value(self.payment_order_type, d.get(frappe.scrub(self.payment_order_type)), ref_field, status)
|
||||
frappe.db.set_value(self.payment_order_type, d.get(ref_doc_field), ref_field, status)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
|
@ -5,6 +5,45 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import getdate
|
||||
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry, make_payment_order
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
|
||||
class TestPaymentOrder(unittest.TestCase):
|
||||
pass
|
||||
def setUp(self):
|
||||
create_bank_account()
|
||||
|
||||
def tearDown(self):
|
||||
for bt in frappe.get_all("Payment Order"):
|
||||
doc = frappe.get_doc("Payment Order", bt.name)
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
def test_payment_order_creation_against_payment_entry(self):
|
||||
purchase_invoice = make_purchase_invoice()
|
||||
payment_entry = get_payment_entry("Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC")
|
||||
payment_entry.reference_no = "_Test_Payment_Order"
|
||||
payment_entry.reference_date = getdate()
|
||||
payment_entry.party_bank_account = "Checking Account - Citi Bank"
|
||||
payment_entry.insert()
|
||||
payment_entry.submit()
|
||||
|
||||
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
|
||||
reference_doc = doc.get("references")[0]
|
||||
self.assertEquals(reference_doc.reference_name, payment_entry.name)
|
||||
self.assertEquals(reference_doc.reference_doctype, "Payment Entry")
|
||||
self.assertEquals(reference_doc.supplier, "_Test Supplier")
|
||||
self.assertEquals(reference_doc.amount, 250)
|
||||
|
||||
def create_payment_order_against_payment_entry(ref_doc, order_type):
|
||||
payment_order = frappe.get_doc(dict(
|
||||
doctype="Payment Order",
|
||||
company="_Test Company",
|
||||
payment_order_type=order_type,
|
||||
company_bank_account="Checking Account - Citi Bank"
|
||||
))
|
||||
doc = make_payment_order(ref_doc.name, payment_order)
|
||||
doc.save()
|
||||
doc.submit()
|
||||
return doc
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2018-07-20 16:38:06.630813",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@ -10,7 +11,6 @@
|
||||
"column_break_4",
|
||||
"supplier",
|
||||
"payment_request",
|
||||
"payment_entry",
|
||||
"mode_of_payment",
|
||||
"bank_account_details",
|
||||
"bank_account",
|
||||
@ -103,17 +103,12 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_entry",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Entry",
|
||||
"options": "Payment Entry",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-05-08 13:56:25.724557",
|
||||
"links": [],
|
||||
"modified": "2020-09-04 08:29:51.014390",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Order Reference",
|
||||
|
@ -55,14 +55,48 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
},
|
||||
callback: (r) => {
|
||||
let pos_docs = r.message;
|
||||
set_form_data(pos_docs, frm)
|
||||
refresh_fields(frm)
|
||||
set_html_data(frm)
|
||||
set_form_data(pos_docs, frm);
|
||||
refresh_fields(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', {
|
||||
closing_amount: (frm, 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.net_total += flt(d.net_total);
|
||||
frm.doc.total_quantity += flt(d.total_qty);
|
||||
add_to_payments(d, frm);
|
||||
add_to_taxes(d, frm);
|
||||
refresh_payments(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 => {
|
||||
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_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 {
|
||||
frm.add_child("payment_reconciliation", {
|
||||
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 => {
|
||||
const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
|
||||
if (tax) {
|
||||
tax.amount += flt(t.tax_amount);
|
||||
if (!remove) tax.amount += flt(t.tax_amount);
|
||||
else tax.amount -= flt(t.tax_amount);
|
||||
} else {
|
||||
frm.add_child("taxes", {
|
||||
account_head: t.account_head,
|
||||
|
@ -279,7 +279,8 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Return (Credit Note)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break1",
|
||||
@ -1578,9 +1579,10 @@
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-29 15:08:39.337385",
|
||||
"modified": "2020-09-07 12:43:09.138720",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
@ -182,8 +182,9 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
def test_pos_returns_with_repayment(self):
|
||||
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': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
|
||||
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500, 'default': 1})
|
||||
pos.insert()
|
||||
pos.submit()
|
||||
|
||||
@ -200,8 +201,9 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
|
||||
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': 'Cash', 'account': 'Cash - _TC', 'amount': 60})
|
||||
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60, 'default': 1})
|
||||
|
||||
pos.insert()
|
||||
pos.submit()
|
||||
|
@ -24,11 +24,20 @@ class POSInvoiceMergeLog(Document):
|
||||
|
||||
def validate_pos_invoice_status(self):
|
||||
for d in self.pos_invoices:
|
||||
status, docstatus = frappe.db.get_value('POS Invoice', d.pos_invoice, ['status', 'docstatus'])
|
||||
status, docstatus, is_return, return_against = frappe.db.get_value(
|
||||
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
|
||||
|
||||
if docstatus != 1:
|
||||
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))
|
||||
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):
|
||||
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]
|
||||
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
|
||||
|
||||
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)
|
||||
else:
|
||||
credit_note = ""
|
||||
|
||||
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
||||
|
||||
|
@ -8,6 +8,8 @@ import unittest
|
||||
from erpnext.stock.get_item_details import get_pos_profile
|
||||
from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes
|
||||
|
||||
test_dependencies = ['Item']
|
||||
|
||||
class TestPOSProfile(unittest.TestCase):
|
||||
def test_pos_profile(self):
|
||||
make_pos_profile()
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
# For license information, please see license.txt
|
||||
|
||||
@ -237,7 +236,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
|
||||
update_args_for_pricing_rule(args)
|
||||
|
||||
pricing_rules = (get_applied_pricing_rules(args)
|
||||
pricing_rules = (get_applied_pricing_rules(args.get('pricing_rules'))
|
||||
if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc))
|
||||
|
||||
if pricing_rules:
|
||||
@ -365,8 +364,9 @@ def set_discount_amount(rate, item_details):
|
||||
item_details.rate = rate
|
||||
|
||||
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items
|
||||
for d in json.loads(pricing_rules):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
|
||||
get_pricing_rule_items)
|
||||
for d in get_applied_pricing_rules(pricing_rules):
|
||||
if not d or not frappe.db.exists("Pricing Rule", d): continue
|
||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||
|
||||
|
@ -447,9 +447,14 @@ def apply_pricing_rule_on_transaction(doc):
|
||||
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
||||
doc.set_missing_values()
|
||||
|
||||
def get_applied_pricing_rules(item_row):
|
||||
return (json.loads(item_row.get("pricing_rules"))
|
||||
if item_row.get("pricing_rules") else [])
|
||||
def get_applied_pricing_rules(pricing_rules):
|
||||
if pricing_rules:
|
||||
if pricing_rules.startswith('['):
|
||||
return json.loads(pricing_rules)
|
||||
else:
|
||||
return pricing_rules.split(',')
|
||||
|
||||
return []
|
||||
|
||||
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
free_item = pricing_rule.free_item
|
||||
|
@ -10,13 +10,15 @@ frappe.ui.form.on('Process Deferred Accounting', {
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
if (frm.doc.company) {
|
||||
type: function(frm) {
|
||||
if (frm.doc.company && frm.doc.type) {
|
||||
frm.set_query("account", function() {
|
||||
return {
|
||||
filters: {
|
||||
'company': frm.doc.company,
|
||||
'root_type': 'Liability',
|
||||
'root_type': frm.doc.type === 'Income' ? 'Liability' : 'Asset',
|
||||
'is_group': 0
|
||||
}
|
||||
};
|
||||
|
@ -60,6 +60,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type",
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account",
|
||||
@ -73,9 +74,10 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-06 18:18:09.852844",
|
||||
"modified": "2020-09-03 18:07:02.463754",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Deferred Accounting",
|
||||
|
@ -25,6 +25,12 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
this.frm.set_df_property("credit_to", "print_hide", 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
if (this.frm.doc.supplier) {
|
||||
this.frm.trigger('supplier');
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(doc) {
|
||||
@ -135,6 +141,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
|
||||
},
|
||||
|
||||
unblock_invoice: function() {
|
||||
|
@ -132,6 +132,11 @@ class PurchaseInvoice(BuyingController):
|
||||
if not self.due_date:
|
||||
self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company, self.bill_date)
|
||||
|
||||
tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||
if tds_category and not for_validate:
|
||||
self.apply_tds = 1
|
||||
self.tax_withholding_category = tds_category
|
||||
|
||||
super(PurchaseInvoice, self).set_missing_values(for_validate)
|
||||
|
||||
def check_conversion_rate(self):
|
||||
@ -400,8 +405,6 @@ class PurchaseInvoice(BuyingController):
|
||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
|
||||
def make_gl_entries(self, gl_entries=None):
|
||||
if not self.grand_total:
|
||||
return
|
||||
if not gl_entries:
|
||||
gl_entries = self.get_gl_entries()
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-05-22 12:43:10",
|
||||
"doctype": "DocType",
|
||||
@ -82,6 +81,7 @@
|
||||
"item_tax_rate",
|
||||
"bom",
|
||||
"include_exploded_items",
|
||||
"purchase_invoice_item",
|
||||
"col_break6",
|
||||
"purchase_order",
|
||||
"po_detail",
|
||||
@ -769,12 +769,21 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "col_break7",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"fieldname": "purchase_invoice_item",
|
||||
"fieldtype": "Data",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Purchase Invoice Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-22 10:37:35.103176",
|
||||
"modified": "2020-08-20 11:48:01.398356",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
@ -447,7 +447,7 @@
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "po_no",
|
||||
"fieldtype": "Small Text",
|
||||
"fieldtype": "Data",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Customer's Purchase Order",
|
||||
@ -1946,7 +1946,7 @@
|
||||
"idx": 181,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 23:31:12.675040",
|
||||
"modified": "2020-08-27 01:56:28.532140",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -13,7 +13,8 @@ def get_data():
|
||||
'Auto Repeat': 'reference_document',
|
||||
},
|
||||
'internal_links': {
|
||||
'Sales Order': ['items', 'sales_order']
|
||||
'Sales Order': ['items', 'sales_order'],
|
||||
'Delivery Note': ['items', 'delivery_note']
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-06-04 11:02:19",
|
||||
"doctype": "DocType",
|
||||
@ -87,6 +86,7 @@
|
||||
"edit_references",
|
||||
"sales_order",
|
||||
"so_detail",
|
||||
"sales_invoice_item",
|
||||
"column_break_74",
|
||||
"delivery_note",
|
||||
"dn_detail",
|
||||
@ -790,12 +790,22 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"fieldname": "sales_invoice_item",
|
||||
"fieldtype": "Data",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Sales Invoice Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-18 12:24:41.749986",
|
||||
"modified": "2020-08-20 11:24:41.749986",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
@ -3,6 +3,22 @@
|
||||
|
||||
frappe.ui.form.on('Shipping Rule', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
frm.set_query("account", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
frm.trigger('toggle_reqd');
|
||||
},
|
||||
calculate_based_on: function(frm) {
|
||||
|
@ -1,134 +1,66 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"beta": 0,
|
||||
"creation": "2018-11-22 23:38:39.668804",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Title",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2020-01-15 17:14:28.951793",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-30 19:41:25.783852",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Category",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
@ -45,8 +45,8 @@ def validate_accounting_period(gl_map):
|
||||
}, as_dict=1)
|
||||
|
||||
if accounting_periods:
|
||||
frappe.throw(_("You can't create accounting entries in the closed accounting period {0}")
|
||||
.format(accounting_periods[0].name), ClosedAccountingPeriod)
|
||||
frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}")
|
||||
.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
|
||||
|
||||
def process_gl_map(gl_map, merge_entries=True):
|
||||
if merge_entries:
|
||||
@ -301,8 +301,9 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
||||
})
|
||||
|
||||
if gl_entries:
|
||||
set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
|
||||
validate_accounting_period(gl_entries)
|
||||
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
||||
set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
|
||||
|
||||
for entry in gl_entries:
|
||||
entry['name'] = None
|
||||
@ -342,7 +343,7 @@ def set_as_cancel(voucher_type, voucher_no):
|
||||
"""
|
||||
Set is_cancelled=1 in all original gl entries for the voucher
|
||||
"""
|
||||
frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1,
|
||||
frappe.db.sql("""UPDATE `tabGL Entry` SET is_cancelled = 1,
|
||||
modified=%s, modified_by=%s
|
||||
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
|
||||
(now(), frappe.session.user, voucher_type, voucher_no))
|
||||
|
@ -71,7 +71,22 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
fieldtype: "Check",
|
||||
default: 0,
|
||||
},
|
||||
]
|
||||
],
|
||||
"formatter": function (value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (column.fieldname.includes('variance')) {
|
||||
|
||||
if (data[column.fieldname] < 0) {
|
||||
value = "<span style='color:red'>" + value + "</span>";
|
||||
}
|
||||
else if (data[column.fieldname] > 0) {
|
||||
value = "<span style='color:green'>" + value + "</span>";
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
|
@ -378,7 +378,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
|
||||
if filters and filters.get('presentation_currency') != d.default_currency:
|
||||
currency_info['company'] = d.name
|
||||
currency_info['company_currency'] = d.default_currency
|
||||
convert_to_presentation_currency(gl_entries, currency_info)
|
||||
convert_to_presentation_currency(gl_entries, currency_info, filters.get('company'))
|
||||
|
||||
for entry in gl_entries:
|
||||
key = entry.account_number or entry.account_name
|
||||
|
@ -173,7 +173,7 @@ class PartyLedgerSummaryReport(object):
|
||||
from `tabGL Entry` gle
|
||||
{join}
|
||||
where
|
||||
gle.docstatus < 2 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
|
||||
gle.docstatus < 2 and gle.is_cancelled = 0 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
|
||||
and gle.posting_date <= %(to_date)s {conditions}
|
||||
order by gle.posting_date
|
||||
""".format(join=join, join_field=join_field, conditions=conditions), self.filters, as_dict=True)
|
||||
@ -248,7 +248,7 @@ class PartyLedgerSummaryReport(object):
|
||||
from
|
||||
`tabGL Entry`
|
||||
where
|
||||
docstatus < 2
|
||||
docstatus < 2 and is_cancelled = 0
|
||||
and (voucher_type, voucher_no) in (
|
||||
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
|
||||
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
|
||||
|
@ -423,7 +423,7 @@ def set_gl_entries_by_account(
|
||||
distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec
|
||||
|
||||
if filters and filters.get('presentation_currency'):
|
||||
convert_to_presentation_currency(gl_entries, get_currency(filters))
|
||||
convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get('company'))
|
||||
|
||||
for entry in gl_entries:
|
||||
gl_entries_by_account.setdefault(entry.account, []).append(entry)
|
||||
|
@ -146,6 +146,12 @@ frappe.query_reports["General Ledger"] = {
|
||||
return frappe.db.get_link_options('Project', txt);
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "include_dimensions",
|
||||
"label": __("Consider Accounting Dimensions"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "show_opening_entries",
|
||||
"label": __("Show Opening Entries"),
|
||||
|
@ -43,8 +43,11 @@ def execute(filters=None):
|
||||
|
||||
|
||||
def validate_filters(filters, account_details):
|
||||
if not filters.get('company'):
|
||||
frappe.throw(_('{0} is mandatory').format(_('Company')))
|
||||
if not filters.get("company"):
|
||||
frappe.throw(_("{0} is mandatory").format(_("Company")))
|
||||
|
||||
if not filters.get("from_date") and not filters.get("to_date"):
|
||||
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
|
||||
|
||||
if filters.get("account") and not account_details.get(filters.account):
|
||||
frappe.throw(_("Account {0} does not exists").format(filters.account))
|
||||
@ -106,15 +109,20 @@ def set_account_currency(filters):
|
||||
return filters
|
||||
|
||||
def get_result(filters, account_details):
|
||||
gl_entries = get_gl_entries(filters)
|
||||
accounting_dimensions = []
|
||||
if filters.get("include_dimensions"):
|
||||
accounting_dimensions = get_accounting_dimensions()
|
||||
|
||||
data = get_data_with_opening_closing(filters, account_details, gl_entries)
|
||||
gl_entries = get_gl_entries(filters, accounting_dimensions)
|
||||
|
||||
data = get_data_with_opening_closing(filters, account_details,
|
||||
accounting_dimensions, gl_entries)
|
||||
|
||||
result = get_result_as_list(data, filters)
|
||||
|
||||
return result
|
||||
|
||||
def get_gl_entries(filters):
|
||||
def get_gl_entries(filters, accounting_dimensions):
|
||||
currency_map = get_currency(filters)
|
||||
select_fields = """, debit, credit, debit_in_account_currency,
|
||||
credit_in_account_currency """
|
||||
@ -128,6 +136,10 @@ def get_gl_entries(filters):
|
||||
filters['company_fb'] = frappe.db.get_value("Company",
|
||||
filters.get("company"), 'default_finance_book')
|
||||
|
||||
dimension_fields = ""
|
||||
if accounting_dimensions:
|
||||
dimension_fields = ', '.join(accounting_dimensions) + ','
|
||||
|
||||
distributed_cost_center_query = ""
|
||||
if filters and filters.get('cost_center'):
|
||||
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
|
||||
@ -141,7 +153,7 @@ def get_gl_entries(filters):
|
||||
party_type,
|
||||
party,
|
||||
voucher_type,
|
||||
voucher_no,
|
||||
voucher_no, {dimension_fields}
|
||||
cost_center, project,
|
||||
against_voucher_type,
|
||||
against_voucher,
|
||||
@ -160,13 +172,14 @@ def get_gl_entries(filters):
|
||||
{conditions}
|
||||
AND posting_date <= %(to_date)s
|
||||
AND cost_center = DCC_allocation.parent
|
||||
""".format(select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
|
||||
""".format(dimension_fields=dimension_fields,select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name as gl_entry, posting_date, account, party_type, party,
|
||||
voucher_type, voucher_no, cost_center, project,
|
||||
voucher_type, voucher_no, {dimension_fields}
|
||||
cost_center, project,
|
||||
against_voucher_type, against_voucher, account_currency,
|
||||
remarks, against, is_opening, creation {select_fields}
|
||||
from `tabGL Entry`
|
||||
@ -174,13 +187,13 @@ def get_gl_entries(filters):
|
||||
{distributed_cost_center_query}
|
||||
{order_by_statement}
|
||||
""".format(
|
||||
select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
|
||||
dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
|
||||
order_by_statement=order_by_statement
|
||||
),
|
||||
filters, as_dict=1)
|
||||
|
||||
if filters.get('presentation_currency'):
|
||||
return convert_to_presentation_currency(gl_entries, currency_map)
|
||||
return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company'))
|
||||
else:
|
||||
return gl_entries
|
||||
|
||||
@ -247,12 +260,12 @@ def get_conditions(filters):
|
||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||
|
||||
|
||||
def get_data_with_opening_closing(filters, account_details, gl_entries):
|
||||
def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries):
|
||||
data = []
|
||||
|
||||
gle_map = initialize_gle_map(gl_entries, filters)
|
||||
|
||||
totals, entries = get_accountwise_gle(filters, gl_entries, gle_map)
|
||||
totals, entries = get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map)
|
||||
|
||||
# Opening for filtered account
|
||||
data.append(totals.opening)
|
||||
@ -318,7 +331,7 @@ def initialize_gle_map(gl_entries, filters):
|
||||
return gle_map
|
||||
|
||||
|
||||
def get_accountwise_gle(filters, gl_entries, gle_map):
|
||||
def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
||||
totals = get_totals_dict()
|
||||
entries = []
|
||||
consolidated_gle = OrderedDict()
|
||||
@ -350,8 +363,11 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
|
||||
if filters.get("group_by") != _('Group by Voucher (Consolidated)'):
|
||||
gle_map[gle.get(group_by)].entries.append(gle)
|
||||
elif filters.get("group_by") == _('Group by Voucher (Consolidated)'):
|
||||
key = (gle.get("voucher_type"), gle.get("voucher_no"),
|
||||
gle.get("account"), gle.get("cost_center"))
|
||||
keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")]
|
||||
for dim in accounting_dimensions:
|
||||
keylist.append(gle.get(dim))
|
||||
keylist.append(gle.get("cost_center"))
|
||||
key = tuple(keylist)
|
||||
if key not in consolidated_gle:
|
||||
consolidated_gle.setdefault(key, gle)
|
||||
else:
|
||||
@ -478,7 +494,19 @@ def get_columns(filters):
|
||||
"options": "Project",
|
||||
"fieldname": "project",
|
||||
"width": 100
|
||||
},
|
||||
}
|
||||
])
|
||||
|
||||
if filters.get("include_dimensions"):
|
||||
for dim in get_accounting_dimensions(as_list = False):
|
||||
columns.append({
|
||||
"label": _(dim.label),
|
||||
"options": dim.label,
|
||||
"fieldname": dim.fieldname,
|
||||
"width": 100
|
||||
})
|
||||
|
||||
columns.extend([
|
||||
{
|
||||
"label": _("Cost Center"),
|
||||
"options": "Cost Center",
|
||||
|
@ -1,13 +1,12 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"add_total_row": 1,
|
||||
"creation": "2013-02-25 17:03:34",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2017-02-24 20:12:22.464240",
|
||||
"modified": "2020-08-13 11:26:39.112352",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Gross Profit",
|
||||
|
@ -6,10 +6,6 @@ from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_dat
|
||||
from frappe.utils import cint, get_datetime_str, formatdate, flt
|
||||
|
||||
__exchange_rates = {}
|
||||
P_OR_L_ACCOUNTS = list(
|
||||
sum(frappe.get_list('Account', fields=['name'], or_filters=[{'root_type': 'Income'}, {'root_type': 'Expense'}], as_list=True), ())
|
||||
)
|
||||
|
||||
|
||||
def get_currency(filters):
|
||||
"""
|
||||
@ -73,18 +69,7 @@ def get_rate_as_at(date, from_currency, to_currency):
|
||||
|
||||
return rate
|
||||
|
||||
|
||||
def is_p_or_l_account(account_name):
|
||||
"""
|
||||
Check if the given `account name` is an `Account` with `root_type` of either 'Income'
|
||||
or 'Expense'.
|
||||
:param account_name:
|
||||
:return: Boolean
|
||||
"""
|
||||
return account_name in P_OR_L_ACCOUNTS
|
||||
|
||||
|
||||
def convert_to_presentation_currency(gl_entries, currency_info):
|
||||
def convert_to_presentation_currency(gl_entries, currency_info, company):
|
||||
"""
|
||||
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
|
||||
in `currency_info`.
|
||||
@ -96,6 +81,9 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
||||
presentation_currency = currency_info['presentation_currency']
|
||||
company_currency = currency_info['company_currency']
|
||||
|
||||
pl_accounts = [d.name for d in frappe.get_list('Account',
|
||||
filters={'report_type': 'Profit and Loss', 'company': company})]
|
||||
|
||||
for entry in gl_entries:
|
||||
account = entry['account']
|
||||
debit = flt(entry['debit'])
|
||||
@ -107,7 +95,7 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
||||
if account_currency != presentation_currency:
|
||||
value = debit or credit
|
||||
|
||||
date = currency_info['report_date'] if not is_p_or_l_account(account) else entry['posting_date']
|
||||
date = entry['posting_date'] if account in pl_accounts else currency_info['report_date']
|
||||
converted_value = convert(value, presentation_currency, company_currency, date)
|
||||
|
||||
if entry.get('debit'):
|
||||
|
@ -146,7 +146,7 @@ class Asset(AccountsController):
|
||||
'assets': assets,
|
||||
'purpose': 'Receipt',
|
||||
'company': self.company,
|
||||
'transaction_date': getdate(nowdate()),
|
||||
'transaction_date': getdate(self.purchase_date),
|
||||
'reference_doctype': reference_doctype,
|
||||
'reference_name': reference_docname
|
||||
}).insert()
|
||||
|
@ -11,6 +11,8 @@ from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
|
||||
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
|
||||
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
|
||||
|
||||
class TestRequestforQuotation(unittest.TestCase):
|
||||
def test_quote_status(self):
|
||||
@ -110,6 +112,23 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
self.assertEqual(supplier_quotation.items[0].qty, 5)
|
||||
self.assertEqual(supplier_quotation.items[0].stock_qty, 10)
|
||||
|
||||
def test_make_rfq_from_opportunity(self):
|
||||
opportunity = make_opportunity(with_items=1)
|
||||
supplier_data = get_supplier_data()
|
||||
rfq = make_rfq(opportunity.name)
|
||||
|
||||
self.assertEqual(len(rfq.get("items")), len(opportunity.get("items")))
|
||||
rfq.message_for_supplier = 'Please supply the specified items at the best possible rates.'
|
||||
|
||||
for item in rfq.items:
|
||||
item.warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
for data in supplier_data:
|
||||
rfq.append('suppliers', data)
|
||||
|
||||
rfq.status = 'Draft'
|
||||
rfq.submit()
|
||||
|
||||
def make_request_for_quotation(**args):
|
||||
"""
|
||||
:param supplier_data: List containing supplier data
|
||||
|
@ -12,7 +12,22 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
reqd: 1,
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"width": "80",
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"width": "80",
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.get_today()
|
||||
},
|
||||
{
|
||||
default: "",
|
||||
options: "Item",
|
||||
label: __("Item"),
|
||||
@ -45,13 +60,12 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldtype: "MultiSelectList",
|
||||
label: __("Supplier Quotation"),
|
||||
options: "Supplier Quotation",
|
||||
fieldname: "supplier_quotation",
|
||||
default: "",
|
||||
get_query: () => {
|
||||
return { filters: { "docstatus": ["<", 2] } }
|
||||
get_data: function(txt) {
|
||||
return frappe.db.get_link_options('Supplier Quotation', txt, {'docstatus': ["<", 2]});
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -63,9 +77,30 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
get_query: () => {
|
||||
return { filters: { "docstatus": ["<", 2] } }
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldtype: "Check",
|
||||
label: __("Include Expired"),
|
||||
fieldname: "include_expired",
|
||||
default: 0
|
||||
}
|
||||
],
|
||||
|
||||
formatter: (value, row, column, data, default_formatter) => {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if(column.fieldname === "valid_till" && data.valid_till){
|
||||
if(frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 1){
|
||||
value = `<div style="color:red">${value}</div>`;
|
||||
}
|
||||
else if (frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 7){
|
||||
value = `<div style="color:darkorange">${value}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
onload: (report) => {
|
||||
// Create a button for setting the default supplier
|
||||
report.page.add_inner_button(__("Select Default Supplier"), () => {
|
||||
|
@ -16,44 +16,49 @@ def execute(filters=None):
|
||||
supplier_quotation_data = get_data(filters, conditions)
|
||||
columns = get_columns()
|
||||
|
||||
data, chart_data = prepare_data(supplier_quotation_data)
|
||||
data, chart_data = prepare_data(supplier_quotation_data, filters)
|
||||
message = get_message()
|
||||
|
||||
return columns, data, None, chart_data
|
||||
return columns, data, message, chart_data
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
if filters.get("item_code"):
|
||||
conditions += " AND sqi.item_code = %(item_code)s"
|
||||
|
||||
if filters.get("supplier_quotation"):
|
||||
conditions += " AND sqi.parent = %(supplier_quotation)s"
|
||||
conditions += " AND sqi.parent in %(supplier_quotation)s"
|
||||
|
||||
if filters.get("request_for_quotation"):
|
||||
conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s"
|
||||
|
||||
if filters.get("supplier"):
|
||||
conditions += " AND sq.supplier in %(supplier)s"
|
||||
|
||||
if not filters.get("include_expired"):
|
||||
conditions += " AND sq.status != 'Expired'"
|
||||
|
||||
return conditions
|
||||
|
||||
def get_data(filters, conditions):
|
||||
if not filters.get("item_code"):
|
||||
return []
|
||||
|
||||
supplier_quotation_data = frappe.db.sql("""SELECT
|
||||
sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
|
||||
sq.supplier
|
||||
sqi.parent, sqi.item_code, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
|
||||
sqi.lead_time_days, sq.supplier, sq.valid_till
|
||||
FROM
|
||||
`tabSupplier Quotation Item` sqi,
|
||||
`tabSupplier Quotation` sq
|
||||
WHERE
|
||||
sqi.item_code = %(item_code)s
|
||||
AND sqi.parent = sq.name
|
||||
sqi.parent = sq.name
|
||||
AND sqi.docstatus < 2
|
||||
AND sq.company = %(company)s
|
||||
AND sq.status != 'Expired'
|
||||
{0}""".format(conditions), filters, as_dict=1)
|
||||
AND sq.transaction_date between %(from_date)s and %(to_date)s
|
||||
{0}
|
||||
order by sq.transaction_date, sqi.item_code""".format(conditions), filters, as_dict=1)
|
||||
|
||||
return supplier_quotation_data
|
||||
|
||||
def prepare_data(supplier_quotation_data):
|
||||
out, suppliers, qty_list = [], [], []
|
||||
def prepare_data(supplier_quotation_data, filters):
|
||||
out, suppliers, qty_list, chart_data = [], [], [], []
|
||||
supplier_wise_map = defaultdict(list)
|
||||
supplier_qty_price_map = {}
|
||||
|
||||
@ -70,17 +75,21 @@ def prepare_data(supplier_quotation_data):
|
||||
exchange_rate = 1
|
||||
|
||||
row = {
|
||||
"item_code": data.get('item_code'),
|
||||
"quotation": data.get("parent"),
|
||||
"qty": data.get("qty"),
|
||||
"price": flt(data.get("rate") * exchange_rate, float_precision),
|
||||
"uom": data.get("uom"),
|
||||
"request_for_quotation": data.get("request_for_quotation"),
|
||||
"valid_till": data.get('valid_till'),
|
||||
"lead_time_days": data.get('lead_time_days')
|
||||
}
|
||||
|
||||
# map for report view of form {'supplier1':[{},{},...]}
|
||||
supplier_wise_map[supplier].append(row)
|
||||
|
||||
# map for chart preparation of the form {'supplier1': {'qty': 'price'}}
|
||||
if filters.get("item_code"):
|
||||
if not supplier in supplier_qty_price_map:
|
||||
supplier_qty_price_map[supplier] = {}
|
||||
supplier_qty_price_map[supplier][row["qty"]] = row["price"]
|
||||
@ -97,6 +106,7 @@ def prepare_data(supplier_quotation_data):
|
||||
for entry in supplier_wise_map[supplier]:
|
||||
out.append(entry)
|
||||
|
||||
if filters.get("item_code"):
|
||||
chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
|
||||
|
||||
return out, chart_data
|
||||
@ -117,9 +127,10 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
|
||||
data_points_map[qty].append(None)
|
||||
|
||||
dataset = []
|
||||
currency_symbol = frappe.db.get_value("Currency", frappe.db.get_default("currency"), "symbol")
|
||||
for qty in qty_list:
|
||||
datapoints = {
|
||||
"name": _("Price for Qty ") + str(qty),
|
||||
"name": currency_symbol + " (Qty " + str(qty) + " )",
|
||||
"values": data_points_map[qty]
|
||||
}
|
||||
dataset.append(datapoints)
|
||||
@ -140,14 +151,21 @@ def get_columns():
|
||||
"label": _("Supplier"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"label": _("Item"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"fieldname": "quotation",
|
||||
"label": _("Supplier Quotation"),
|
||||
"fieldname": "uom",
|
||||
"label": _("UOM"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Quotation",
|
||||
"width": 200
|
||||
"options": "UOM",
|
||||
"width": 90
|
||||
},
|
||||
{
|
||||
"fieldname": "qty",
|
||||
@ -163,19 +181,43 @@ def get_columns():
|
||||
"width": 110
|
||||
},
|
||||
{
|
||||
"fieldname": "uom",
|
||||
"label": _("UOM"),
|
||||
"fieldname": "quotation",
|
||||
"label": _("Supplier Quotation"),
|
||||
"fieldtype": "Link",
|
||||
"options": "UOM",
|
||||
"width": 90
|
||||
"options": "Supplier Quotation",
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"fieldname": "valid_till",
|
||||
"label": _("Valid Till"),
|
||||
"fieldtype": "Date",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "lead_time_days",
|
||||
"label": _("Lead Time (Days)"),
|
||||
"fieldtype": "Int",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "request_for_quotation",
|
||||
"label": _("Request for Quotation"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Request for Quotation",
|
||||
"width": 200
|
||||
"width": 150
|
||||
}
|
||||
]
|
||||
|
||||
return columns
|
||||
|
||||
def get_message():
|
||||
return """<span class="indicator">
|
||||
Valid till :
|
||||
</span>
|
||||
<span class="indicator orange">
|
||||
Expires in a week or less
|
||||
</span>
|
||||
|
||||
<span class="indicator red">
|
||||
Expires today / Already Expired
|
||||
</span>"""
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "field:id",
|
||||
"creation": "2019-06-05 12:07:02.634534",
|
||||
"doctype": "DocType",
|
||||
@ -14,6 +15,7 @@
|
||||
"contact",
|
||||
"contact_name",
|
||||
"column_break_10",
|
||||
"customer",
|
||||
"lead",
|
||||
"lead_name",
|
||||
"section_break_5",
|
||||
@ -28,7 +30,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Call Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "id",
|
||||
@ -125,10 +128,19 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Lead Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"label": "Customer",
|
||||
"options": "Customer",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"modified": "2019-08-06 05:46:53.144683",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-25 17:08:34.085731",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Communication",
|
||||
"name": "Call Log",
|
||||
|
@ -16,6 +16,9 @@ class CallLog(Document):
|
||||
self.contact = get_contact_with_phone_number(number)
|
||||
self.lead = get_lead_with_phone_number(number)
|
||||
|
||||
contact = frappe.get_doc("Contact", self.contact)
|
||||
self.customer = contact.get_link_for("Customer")
|
||||
|
||||
def after_insert(self):
|
||||
self.trigger_call_popup()
|
||||
|
||||
|
@ -7,6 +7,7 @@ import json
|
||||
from frappe import _, throw
|
||||
from frappe.utils import (today, flt, cint, fmt_money, formatdate,
|
||||
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.setup.utils import get_exchange_rate
|
||||
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
|
||||
@ -325,7 +326,7 @@ class AccountsController(TransactionBase):
|
||||
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
|
||||
|
||||
elif pricing_rule_args.get("validate_applied_rule"):
|
||||
for pricing_rule in get_applied_pricing_rules(item):
|
||||
for pricing_rule in get_applied_pricing_rules(item.get('pricing_rules')):
|
||||
pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule)
|
||||
for field in ['discount_percentage', 'discount_amount', 'rate']:
|
||||
if item.get(field) < pricing_rule_doc.get(field):
|
||||
@ -1208,7 +1209,7 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
|
||||
set_child_tax_template_and_map(item, child_item, p_doc)
|
||||
return child_item
|
||||
|
||||
def check_and_delete_children(parent, data):
|
||||
def validate_and_delete_children(parent, data):
|
||||
deleted_children = []
|
||||
updated_item_names = [d.get("docname") for d in data]
|
||||
for item in parent.items:
|
||||
@ -1235,18 +1236,37 @@ def check_and_delete_children(parent, data):
|
||||
|
||||
@frappe.whitelist()
|
||||
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:
|
||||
doc.check_permission(perm_type)
|
||||
except:
|
||||
action = "add" if perm_type == 'create' else "update"
|
||||
frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions"))
|
||||
except frappe.PermissionError:
|
||||
actions = { 'create': 'add', 'write': 'update', 'cancel': 'remove' }
|
||||
|
||||
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 do not have workflow access to update this document."), title=_("Insufficient Workflow Permissions"))
|
||||
|
||||
def get_new_child_item(item_row):
|
||||
if parent_doctype == "Sales Order":
|
||||
return set_sales_order_defaults(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)
|
||||
new_child_function = set_sales_order_defaults if parent_doctype == "Sales Order" else set_purchase_order_defaults
|
||||
return new_child_function(parent_doctype, parent_doctype_name, child_docname, item_row)
|
||||
|
||||
def validate_quantity(child_item, d):
|
||||
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
|
||||
@ -1260,16 +1280,17 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
|
||||
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:
|
||||
new_child_flag = False
|
||||
if not d.get("docname"):
|
||||
new_child_flag = True
|
||||
check_permissions(parent, 'create')
|
||||
check_doc_permissions(parent, 'create')
|
||||
child_item = get_new_child_item(d)
|
||||
else:
|
||||
check_permissions(parent, 'write')
|
||||
check_doc_permissions(parent, 'write')
|
||||
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
|
||||
|
||||
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
|
||||
@ -1376,6 +1397,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
parent.update_prevdoc_status('submit')
|
||||
parent.update_delivery_status()
|
||||
|
||||
parent.reload()
|
||||
validate_workflow_conditions(parent)
|
||||
|
||||
parent.update_blanket_order()
|
||||
parent.update_billing_percentage()
|
||||
parent.set_status()
|
||||
|
@ -276,6 +276,9 @@ class BuyingController(StockController):
|
||||
qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
|
||||
|
||||
for item in self.get('items'):
|
||||
if not item.purchase_order:
|
||||
continue
|
||||
|
||||
# reset raw_material cost
|
||||
item.rm_supp_cost = 0
|
||||
|
||||
@ -288,6 +291,12 @@ class BuyingController(StockController):
|
||||
|
||||
fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
|
||||
|
||||
if not fg_yet_to_be_received:
|
||||
frappe.throw(_("Row #{0}: Item {1} is already fully received in Purchase Order {2}")
|
||||
.format(item.idx, frappe.bold(item.item_code),
|
||||
frappe.utils.get_link_to_form("Purchase Order", item.purchase_order)),
|
||||
title=_("Limit Crossed"))
|
||||
|
||||
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
|
||||
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
|
||||
|
||||
@ -559,9 +568,19 @@ class BuyingController(StockController):
|
||||
"serial_no": cstr(d.serial_no).strip()
|
||||
})
|
||||
if self.is_return:
|
||||
original_incoming_rate = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": self.return_against,
|
||||
"item_code": d.item_code}, "incoming_rate")
|
||||
filters = {
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.return_against,
|
||||
"item_code": d.item_code
|
||||
}
|
||||
|
||||
if (self.doctype == "Purchase Invoice" and self.update_stock
|
||||
and d.get("purchase_invoice_item")):
|
||||
filters["voucher_detail_no"] = d.purchase_invoice_item
|
||||
elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"):
|
||||
filters["voucher_detail_no"] = d.purchase_receipt_item
|
||||
|
||||
original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate")
|
||||
|
||||
sle.update({
|
||||
"outgoing_rate": original_incoming_rate
|
||||
|
@ -497,24 +497,18 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions, bin_conditions = [], []
|
||||
filter_dict = get_doctype_wise_filters(filters)
|
||||
|
||||
sub_query = """ select round(`tabBin`.actual_qty, 2) from `tabBin`
|
||||
where `tabBin`.warehouse = `tabWarehouse`.name
|
||||
{bin_conditions} """.format(
|
||||
bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),
|
||||
bin_conditions, ignore_permissions=True))
|
||||
|
||||
query = """select `tabWarehouse`.name,
|
||||
CONCAT_WS(" : ", "Actual Qty", ifnull( ({sub_query}), 0) ) as actual_qty
|
||||
from `tabWarehouse`
|
||||
CONCAT_WS(" : ", "Actual Qty", ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty
|
||||
from `tabWarehouse` left join `tabBin`
|
||||
on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions}
|
||||
where
|
||||
`tabWarehouse`.`{key}` like {txt}
|
||||
{fcond} {mcond}
|
||||
order by
|
||||
`tabWarehouse`.name desc
|
||||
order by ifnull(`tabBin`.actual_qty, 0) desc
|
||||
limit
|
||||
{start}, {page_len}
|
||||
""".format(
|
||||
sub_query=sub_query,
|
||||
bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),bin_conditions, ignore_permissions=True),
|
||||
key=searchfield,
|
||||
fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions),
|
||||
mcond=get_match_cond(doctype),
|
||||
|
@ -242,7 +242,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
'type': data.type,
|
||||
'amount': -1 * paid_amount,
|
||||
'base_amount': -1 * base_paid_amount,
|
||||
'account': data.account
|
||||
'account': data.account,
|
||||
'default': data.default
|
||||
})
|
||||
if doc.is_pos:
|
||||
doc.paid_amount = -1 * source.paid_amount
|
||||
@ -281,6 +282,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||
target_doc.po_detail = source_doc.po_detail
|
||||
target_doc.pr_detail = source_doc.pr_detail
|
||||
target_doc.purchase_invoice_item = source_doc.name
|
||||
|
||||
elif doctype == "Delivery Note":
|
||||
target_doc.against_sales_order = source_doc.against_sales_order
|
||||
target_doc.against_sales_invoice = source_doc.against_sales_invoice
|
||||
@ -296,6 +299,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.so_detail = source_doc.so_detail
|
||||
target_doc.dn_detail = source_doc.dn_detail
|
||||
target_doc.expense_account = source_doc.expense_account
|
||||
target_doc.sales_invoice_item = source_doc.name
|
||||
if default_warehouse_for_sales_return:
|
||||
target_doc.warehouse = default_warehouse_for_sales_return
|
||||
|
||||
|
@ -217,7 +217,9 @@ class SellingController(StockController):
|
||||
'target_warehouse': p.target_warehouse,
|
||||
'company': self.company,
|
||||
'voucher_type': self.doctype,
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'delivery_note_item': d.get("dn_detail")
|
||||
}))
|
||||
else:
|
||||
il.append(frappe._dict({
|
||||
@ -233,7 +235,9 @@ class SellingController(StockController):
|
||||
'target_warehouse': d.target_warehouse,
|
||||
'company': self.company,
|
||||
'voucher_type': self.doctype,
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'delivery_note_item': d.get("dn_detail")
|
||||
}))
|
||||
return il
|
||||
|
||||
@ -302,7 +306,11 @@ class SellingController(StockController):
|
||||
d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
|
||||
return_rate = 0
|
||||
if cint(self.is_return) and self.return_against and self.docstatus==1:
|
||||
return_rate = self.get_incoming_rate_for_return(d.item_code, self.return_against)
|
||||
against_document_no = (d.get("sales_invoice_item")
|
||||
if self.doctype == "Sales Invoice" else d.get("delivery_note_item"))
|
||||
|
||||
return_rate = self.get_incoming_rate_for_return(d.item_code,
|
||||
self.return_against, against_document_no)
|
||||
|
||||
# On cancellation or if return entry submission, make stock ledger entry for
|
||||
# target warehouse first, to update serial no values properly
|
||||
|
@ -255,7 +255,7 @@ class StatusUpdater(Document):
|
||||
args['second_source_condition'] = """ + ifnull((select sum(%(second_source_field)s)
|
||||
from `tab%(second_source_dt)s`
|
||||
where `%(second_join_field)s`="%(detail_id)s"
|
||||
and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s), 0) """ % args
|
||||
and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s FOR UPDATE), 0)""" % args
|
||||
|
||||
if args['detail_id']:
|
||||
if not args.get("extra_cond"): args["extra_cond"] = ""
|
||||
|
@ -301,14 +301,19 @@ class StockController(AccountsController):
|
||||
|
||||
return serialized_items
|
||||
|
||||
def get_incoming_rate_for_return(self, item_code, against_document):
|
||||
def get_incoming_rate_for_return(self, item_code, against_document, against_document_no=None):
|
||||
incoming_rate = 0.0
|
||||
cond = ''
|
||||
if against_document and item_code:
|
||||
if against_document_no:
|
||||
cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no))
|
||||
|
||||
incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where voucher_type = %s and voucher_no = %s
|
||||
and item_code = %s limit 1""",
|
||||
and item_code = %s {0} limit 1""".format(cond),
|
||||
(self.doctype, against_document, item_code))
|
||||
|
||||
incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
|
||||
|
||||
return incoming_rate
|
||||
|
@ -9,6 +9,7 @@ from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
|
||||
from erpnext.controllers.accounts_controller import validate_conversion_rate, \
|
||||
validate_taxes_and_charges, validate_inclusive_tax
|
||||
from erpnext.stock.get_item_details import _get_item_tax_template
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
|
||||
|
||||
class calculate_taxes_and_totals(object):
|
||||
def __init__(self, doc):
|
||||
@ -607,7 +608,7 @@ class calculate_taxes_and_totals(object):
|
||||
base_rate_with_margin = 0.0
|
||||
if item.price_list_rate:
|
||||
if item.pricing_rules and not self.doc.ignore_pricing_rule:
|
||||
for d in json.loads(item.pricing_rules):
|
||||
for d in get_applied_pricing_rules(item.pricing_rules):
|
||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||
|
||||
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
|
||||
|
@ -267,6 +267,9 @@ def make_quotation(source_name, target_doc=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_request_for_quotation(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.conversion_factor = 1.0
|
||||
|
||||
doclist = get_mapped_doc("Opportunity", source_name, {
|
||||
"Opportunity": {
|
||||
"doctype": "Request for Quotation"
|
||||
@ -277,7 +280,8 @@ def make_request_for_quotation(source_name, target_doc=None):
|
||||
["name", "opportunity_item"],
|
||||
["parent", "opportunity"],
|
||||
["uom", "uom"]
|
||||
]
|
||||
],
|
||||
"postprocess": update_item
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
@ -325,7 +329,7 @@ def auto_close_opportunity():
|
||||
doc.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_opportunity_from_communication(communication, ignore_communication_links=False):
|
||||
def make_opportunity_from_communication(communication, company, ignore_communication_links=False):
|
||||
from erpnext.crm.doctype.lead.lead import make_lead_from_communication
|
||||
doc = frappe.get_doc("Communication", communication)
|
||||
|
||||
@ -337,6 +341,7 @@ def make_opportunity_from_communication(communication, ignore_communication_link
|
||||
|
||||
opportunity = frappe.get_doc({
|
||||
"doctype": "Opportunity",
|
||||
"company": company,
|
||||
"opportunity_from": opportunity_from,
|
||||
"party_name": lead
|
||||
}).insert(ignore_permissions=True)
|
||||
|
@ -82,7 +82,8 @@ def make_opportunity(**args):
|
||||
if args.with_items:
|
||||
opp_doc.append('items', {
|
||||
"item_code": args.item_code or "_Test Item",
|
||||
"qty": args.qty or 1
|
||||
"qty": args.qty or 1,
|
||||
"uom": "_Test UOM"
|
||||
})
|
||||
|
||||
opp_doc.insert()
|
||||
|
@ -30,14 +30,14 @@ frappe.ui.form.on('Social Media Post', {
|
||||
let color = frm.doc.twitter_post_id ? "green" : "red";
|
||||
let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">Twitter : ${status} </span></span>
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>Twitter : ${status} </span></span>
|
||||
</div>` ;
|
||||
}
|
||||
if (frm.doc.linkedin){
|
||||
let color = frm.doc.linkedin_post_id ? "green" : "red";
|
||||
let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">LinkedIn : ${status} </span></span>
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>LinkedIn : ${status} </span></span>
|
||||
</div>` ;
|
||||
}
|
||||
html = `<div class="row">${html}</div>`;
|
||||
|
52
erpnext/crm/report/lead_details/lead_details.js
Normal file
52
erpnext/crm/report/lead_details/lead_details.js
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Lead Details"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -12),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today(),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"status",
|
||||
"label": __("Status"),
|
||||
"fieldtype": "Select",
|
||||
options: [
|
||||
{ "value": "Lead", "label": __("Lead") },
|
||||
{ "value": "Open", "label": __("Open") },
|
||||
{ "value": "Replied", "label": __("Replied") },
|
||||
{ "value": "Opportunity", "label": __("Opportunity") },
|
||||
{ "value": "Quotation", "label": __("Quotation") },
|
||||
{ "value": "Lost Quotation", "label": __("Lost Quotation") },
|
||||
{ "value": "Interested", "label": __("Interested") },
|
||||
{ "value": "Converted", "label": __("Converted") },
|
||||
{ "value": "Do Not Contact", "label": __("Do Not Contact") },
|
||||
],
|
||||
},
|
||||
{
|
||||
"fieldname":"territory",
|
||||
"label": __("Territory"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
}
|
||||
]
|
||||
};
|
@ -7,16 +7,15 @@
|
||||
"doctype": "Report",
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2020-01-22 16:51:56.591110",
|
||||
"modified": "2020-07-26 23:59:49.897577",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lead Details",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"query": "SELECT\n `tabLead`.name as \"Lead Id:Link/Lead:120\",\n `tabLead`.lead_name as \"Lead Name::120\",\n\t`tabLead`.company_name as \"Company Name::120\",\n\t`tabLead`.status as \"Status::120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2)\n\t) as 'Address::180',\n\t`tabAddress`.state as \"State::100\",\n\t`tabAddress`.pincode as \"Pincode::70\",\n\t`tabAddress`.country as \"Country::100\",\n\t`tabLead`.phone as \"Phone::100\",\n\t`tabLead`.mobile_no as \"Mobile No::100\",\n\t`tabLead`.email_id as \"Email Id::120\",\n\t`tabLead`.lead_owner as \"Lead Owner::120\",\n\t`tabLead`.source as \"Source::120\",\n\t`tabLead`.territory as \"Territory::120\",\n\t`tabLead`.notes as \"Notes::360\",\n `tabLead`.owner as \"Owner:Link/User:120\"\nFROM\n\t`tabLead`\n\tleft join `tabDynamic Link` on (\n\t\t`tabDynamic Link`.link_name=`tabLead`.name \n\t\tand `tabDynamic Link`.parenttype = 'Address'\n\t)\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.name=`tabDynamic Link`.parent\n\t)\nWHERE\n\t`tabLead`.docstatus<2\nORDER BY\n\t`tabLead`.name asc",
|
||||
"ref_doctype": "Lead",
|
||||
"report_name": "Lead Details",
|
||||
"report_type": "Query Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Sales User"
|
||||
|
158
erpnext/crm/report/lead_details/lead_details.py
Normal file
158
erpnext/crm/report/lead_details/lead_details.py
Normal file
@ -0,0 +1,158 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
import frappe
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = get_columns(), get_data(filters)
|
||||
return columns, data
|
||||
|
||||
def get_columns():
|
||||
columns = [
|
||||
{
|
||||
"label": _("Lead"),
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"options": "Lead",
|
||||
"width": 150,
|
||||
},
|
||||
{
|
||||
"label": _("Lead Name"),
|
||||
"fieldname": "lead_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname":"status",
|
||||
"label": _("Status"),
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname":"lead_owner",
|
||||
"label": _("Lead Owner"),
|
||||
"fieldtype": "Link",
|
||||
"options": "User",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": _("Source"),
|
||||
"fieldname": "source",
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Email"),
|
||||
"fieldname": "email_id",
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Mobile"),
|
||||
"fieldname": "mobile_no",
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Phone"),
|
||||
"fieldname": "phone",
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Owner"),
|
||||
"fieldname": "owner",
|
||||
"fieldtype": "Link",
|
||||
"options": "user",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Company"),
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname":"address",
|
||||
"label": _("Address"),
|
||||
"fieldtype": "Data",
|
||||
"width": 130
|
||||
},
|
||||
{
|
||||
"fieldname":"state",
|
||||
"label": _("State"),
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname":"pincode",
|
||||
"label": _("Postal Code"),
|
||||
"fieldtype": "Data",
|
||||
"width": 90
|
||||
},
|
||||
{
|
||||
"fieldname":"country",
|
||||
"label": _("Country"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Country",
|
||||
"width": 100
|
||||
},
|
||||
|
||||
]
|
||||
return columns
|
||||
|
||||
def get_data(filters):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
`tabLead`.name,
|
||||
`tabLead`.lead_name,
|
||||
`tabLead`.status,
|
||||
`tabLead`.lead_owner,
|
||||
`tabLead`.territory,
|
||||
`tabLead`.source,
|
||||
`tabLead`.email_id,
|
||||
`tabLead`.mobile_no,
|
||||
`tabLead`.phone,
|
||||
`tabLead`.owner,
|
||||
`tabLead`.company,
|
||||
concat_ws(', ',
|
||||
trim(',' from `tabAddress`.address_line1),
|
||||
trim(',' from tabAddress.address_line2)
|
||||
) AS address,
|
||||
`tabAddress`.state,
|
||||
`tabAddress`.pincode,
|
||||
`tabAddress`.country
|
||||
FROM
|
||||
`tabLead` left join `tabDynamic Link` on (
|
||||
`tabLead`.name = `tabDynamic Link`.link_name and
|
||||
`tabDynamic Link`.parenttype = 'Address')
|
||||
left join `tabAddress` on (
|
||||
`tabAddress`.name=`tabDynamic Link`.parent)
|
||||
WHERE
|
||||
company = %(company)s
|
||||
AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s
|
||||
{conditions}
|
||||
ORDER BY
|
||||
`tabLead`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1)
|
||||
|
||||
def get_conditions(filters) :
|
||||
conditions = []
|
||||
|
||||
if filters.get("territory"):
|
||||
conditions.append(" and `tabLead`.territory=%(territory)s")
|
||||
|
||||
if filters.get("status"):
|
||||
conditions.append(" and `tabLead`.status=%(status)s")
|
||||
|
||||
return " ".join(conditions) if conditions else ""
|
||||
|
67
erpnext/crm/report/lost_opportunity/lost_opportunity.js
Normal file
67
erpnext/crm/report/lost_opportunity/lost_opportunity.js
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Lost Opportunity"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -12),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today(),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"lost_reason",
|
||||
"label": __("Lost Reason"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Opportunity Lost Reason"
|
||||
},
|
||||
{
|
||||
"fieldname":"territory",
|
||||
"label": __("Territory"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory"
|
||||
},
|
||||
{
|
||||
"fieldname":"opportunity_from",
|
||||
"label": __("Opportunity From"),
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"name": ["in", ["Customer", "Lead"]],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"party_name",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "opportunity_from"
|
||||
},
|
||||
{
|
||||
"fieldname":"contact_by",
|
||||
"label": __("Next Contact By"),
|
||||
"fieldtype": "Link",
|
||||
"options": "User"
|
||||
},
|
||||
]
|
||||
};
|
@ -1,13 +1,14 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"creation": "2018-12-31 16:30:57.188837",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"json": "{\"order_by\": \"`tabOpportunity`.`modified` desc\", \"filters\": [[\"Opportunity\", \"status\", \"=\", \"Lost\"]], \"fields\": [[\"name\", \"Opportunity\"], [\"opportunity_from\", \"Opportunity\"], [\"party_name\", \"Opportunity\"], [\"customer_name\", \"Opportunity\"], [\"opportunity_type\", \"Opportunity\"], [\"status\", \"Opportunity\"], [\"contact_by\", \"Opportunity\"], [\"docstatus\", \"Opportunity\"], [\"lost_reason\", \"Lost Reason Detail\"]], \"add_totals_row\": 0, \"add_total_row\": 0, \"page_length\": 20}",
|
||||
"modified": "2019-06-26 16:33:08.083618",
|
||||
"modified": "2020-07-29 15:49:02.848845",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lost Opportunity",
|
||||
@ -15,7 +16,7 @@
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Opportunity",
|
||||
"report_name": "Lost Opportunity",
|
||||
"report_type": "Report Builder",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Sales User"
|
||||
|
131
erpnext/crm/report/lost_opportunity/lost_opportunity.py
Normal file
131
erpnext/crm/report/lost_opportunity/lost_opportunity.py
Normal file
@ -0,0 +1,131 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
import frappe
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = get_columns(), get_data(filters)
|
||||
return columns, data
|
||||
|
||||
def get_columns():
|
||||
columns = [
|
||||
{
|
||||
"label": _("Opportunity"),
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"options": "Opportunity",
|
||||
"width": 170,
|
||||
},
|
||||
{
|
||||
"label": _("Opportunity From"),
|
||||
"fieldname": "opportunity_from",
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"width": 130
|
||||
},
|
||||
{
|
||||
"label": _("Party"),
|
||||
"fieldname":"party_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "opportunity_from",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"label": _("Customer/Lead Name"),
|
||||
"fieldname":"customer_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Opportunity Type"),
|
||||
"fieldname": "opportunity_type",
|
||||
"fieldtype": "Data",
|
||||
"width": 130
|
||||
},
|
||||
{
|
||||
"label": _("Lost Reasons"),
|
||||
"fieldname": "lost_reason",
|
||||
"fieldtype": "Data",
|
||||
"width": 220
|
||||
},
|
||||
{
|
||||
"label": _("Sales Stage"),
|
||||
"fieldname": "sales_stage",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Stage",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Next Contact By"),
|
||||
"fieldname": "contact_by",
|
||||
"fieldtype": "Link",
|
||||
"options": "User",
|
||||
"width": 150
|
||||
}
|
||||
]
|
||||
return columns
|
||||
|
||||
def get_data(filters):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
`tabOpportunity`.name,
|
||||
`tabOpportunity`.opportunity_from,
|
||||
`tabOpportunity`.party_name,
|
||||
`tabOpportunity`.customer_name,
|
||||
`tabOpportunity`.opportunity_type,
|
||||
`tabOpportunity`.contact_by,
|
||||
GROUP_CONCAT(`tabOpportunity Lost Reason Detail`.lost_reason separator ', ') lost_reason,
|
||||
`tabOpportunity`.sales_stage,
|
||||
`tabOpportunity`.territory
|
||||
FROM
|
||||
`tabOpportunity`
|
||||
{join}
|
||||
WHERE
|
||||
`tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s
|
||||
AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s
|
||||
{conditions}
|
||||
GROUP BY
|
||||
`tabOpportunity`.name
|
||||
ORDER BY
|
||||
`tabOpportunity`.creation asc """.format(conditions=get_conditions(filters), join=get_join(filters)), filters, as_dict=1)
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = []
|
||||
|
||||
if filters.get("territory"):
|
||||
conditions.append(" and `tabOpportunity`.territory=%(territory)s")
|
||||
|
||||
if filters.get("opportunity_from"):
|
||||
conditions.append(" and `tabOpportunity`.opportunity_from=%(opportunity_from)s")
|
||||
|
||||
if filters.get("party_name"):
|
||||
conditions.append(" and `tabOpportunity`.party_name=%(party_name)s")
|
||||
|
||||
if filters.get("contact_by"):
|
||||
conditions.append(" and `tabOpportunity`.contact_by=%(contact_by)s")
|
||||
|
||||
return " ".join(conditions) if conditions else ""
|
||||
|
||||
def get_join(filters):
|
||||
join = """LEFT JOIN `tabOpportunity Lost Reason Detail`
|
||||
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
|
||||
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name"""
|
||||
|
||||
if filters.get("lost_reason"):
|
||||
join = """JOIN `tabOpportunity Lost Reason Detail`
|
||||
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
|
||||
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and
|
||||
`tabOpportunity Lost Reason Detail`.lost_reason = '{0}'
|
||||
""".format(filters.get("lost_reason"))
|
||||
|
||||
return join
|
@ -3,15 +3,15 @@
|
||||
|
||||
frappe.ui.form.on('Student', {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch("guardian", "guardian_name", "guardian_name");
|
||||
frm.add_fetch("student", "title", "full_name");
|
||||
frm.add_fetch("student", "gender", "gender");
|
||||
frm.add_fetch("student", "date_of_birth", "date_of_birth");
|
||||
frm.add_fetch('guardian', 'guardian_name', 'guardian_name');
|
||||
frm.add_fetch('student', 'title', 'full_name');
|
||||
frm.add_fetch('student', 'gender', 'gender');
|
||||
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 {
|
||||
"filters": {
|
||||
"name": ["!=", doc.name]
|
||||
'filters': {
|
||||
'name': ['!=', doc.name]
|
||||
}
|
||||
};
|
||||
})
|
||||
@ -25,6 +25,12 @@ frappe.ui.form.on('Student', {
|
||||
{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);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -102,7 +102,6 @@
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
"label": "Student Email Address",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
@ -255,7 +254,7 @@
|
||||
],
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-07-23 18:14:06.366442",
|
||||
"modified": "2020-09-07 19:28:08.914568",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Student",
|
||||
|
@ -7,7 +7,7 @@ frappe.ui.form.on("Student Applicant", {
|
||||
},
|
||||
|
||||
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.set_value("application_status", "Approved");
|
||||
frm.save_or_update();
|
||||
@ -20,10 +20,11 @@ frappe.ui.form.on("Student Applicant", {
|
||||
}, '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.events.enroll(frm)
|
||||
}).addClass("btn-primary");
|
||||
|
||||
frm.add_custom_button(__("Reject"), function() {
|
||||
frm.set_value("application_status", "Rejected");
|
||||
frm.save_or_update();
|
||||
@ -35,7 +36,13 @@ frappe.ui.form.on("Student Applicant", {
|
||||
frappe.hide_msgprint(true);
|
||||
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) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,40 @@
|
||||
{
|
||||
"cards": [
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Marketplace",
|
||||
"links": "[\n {\n \"description\": \"Woocommerce marketplace settings\",\n \"label\": \"Woocommerce Settings\",\n \"name\": \"Woocommerce Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Amazon MWS settings\",\n \"label\": \"Amazon MWS Settings\",\n \"name\": \"Amazon MWS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Shopify settings\",\n \"label\": \"Shopify Settings\",\n \"name\": \"Shopify Settings\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Payments",
|
||||
"links": "[\n {\n \"description\": \"GoCardless payment gateway settings\",\n \"label\": \"GoCardless Settings\",\n \"name\": \"GoCardless Settings\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Settings",
|
||||
"links": "[\n {\n \"description\": \"Plaid settings\",\n \"label\": \"Plaid Settings\",\n \"name\": \"Plaid Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Exotel settings\",\n \"label\": \"Exotel Settings\",\n \"name\": \"Exotel Settings\",\n \"type\": \"doctype\"\n }\n]"
|
||||
}
|
||||
],
|
||||
"category": "Modules",
|
||||
"charts": [],
|
||||
"creation": "2020-08-20 19:30:48.138801",
|
||||
"developer_mode_only": 0,
|
||||
"disable_user_customization": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"extends": "Integrations",
|
||||
"extends_another_page": 1,
|
||||
"hide_custom": 1,
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "ERPNext Integrations",
|
||||
"modified": "2020-08-23 16:30:51.494655",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "ERPNext Integrations",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": []
|
||||
}
|
@ -7,6 +7,8 @@ import unittest
|
||||
import frappe
|
||||
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_clinical_procedure_template
|
||||
|
||||
test_dependencies = ['Item']
|
||||
|
||||
class TestClinicalProcedure(unittest.TestCase):
|
||||
def test_procedure_template_item(self):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
|
@ -34,10 +34,10 @@ frappe.ui.form.on('Lab Test', {
|
||||
if (frm.doc.docstatus === 1 && frm.doc.status !== 'Approved' && frm.doc.status !== 'Rejected') {
|
||||
frm.add_custom_button(__('Approve'), function () {
|
||||
status_update(1, frm);
|
||||
});
|
||||
}, __('Actions'));
|
||||
frm.add_custom_button(__('Reject'), function () {
|
||||
status_update(0, frm);
|
||||
});
|
||||
}, __('Actions'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,23 +179,6 @@ var show_lab_tests = function (frm, lab_test_list) {
|
||||
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 number = frm.doc.mobile;
|
||||
|
||||
@ -203,7 +186,7 @@ var make_dialog = function (frm, emailed, printed) {
|
||||
title: 'Send SMS',
|
||||
width: 400,
|
||||
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: 'message', fieldtype: 'Small Text', label: 'Message', reqd: 1 }
|
||||
],
|
||||
@ -217,22 +200,22 @@ var make_dialog = function (frm, emailed, printed) {
|
||||
dialog.hide();
|
||||
}
|
||||
});
|
||||
if (frm.doc.report_preference == 'Print') {
|
||||
if (frm.doc.report_preference === 'Print') {
|
||||
dialog.set_values({
|
||||
'sms_type': 'Printed',
|
||||
'result_format': 'Printed',
|
||||
'number': number,
|
||||
'message': printed
|
||||
});
|
||||
} else {
|
||||
dialog.set_values({
|
||||
'sms_type': 'Emailed',
|
||||
'result_format': 'Emailed',
|
||||
'number': number,
|
||||
'message': emailed
|
||||
});
|
||||
}
|
||||
var fd = dialog.fields_dict;
|
||||
$(fd.sms_type.input).change(function () {
|
||||
if (dialog.get_value('sms_type') == 'Emailed') {
|
||||
$(fd.result_format.input).change(function () {
|
||||
if (dialog.get_value('result_format') === 'Emailed') {
|
||||
dialog.set_values({
|
||||
'number': number,
|
||||
'message': emailed
|
||||
|
@ -84,7 +84,7 @@
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Series",
|
||||
"options": "LP-",
|
||||
"options": "HLC-LAB-.YYYY.-",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1,
|
||||
"reqd": 1
|
||||
@ -197,11 +197,10 @@
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "Draft\nCompleted\nApproved\nRejected\nCancelled",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@ -249,8 +248,8 @@
|
||||
{
|
||||
"fieldname": "result_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"label": "Result Date",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@ -354,7 +353,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "sb_normal",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Compound Test Result"
|
||||
},
|
||||
{
|
||||
"fieldname": "normal_test_items",
|
||||
@ -369,11 +369,13 @@
|
||||
{
|
||||
"depends_on": "descriptive_toggle",
|
||||
"fieldname": "organisms_section",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Organism Test Result"
|
||||
},
|
||||
{
|
||||
"fieldname": "sb_sensitivity",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Sensitivity Test Result"
|
||||
},
|
||||
{
|
||||
"fieldname": "sensitivity_test_items",
|
||||
@ -383,8 +385,10 @@
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "sb_comments",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Comments"
|
||||
},
|
||||
{
|
||||
"fieldname": "lab_test_comment",
|
||||
@ -531,7 +535,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "sb_descriptive",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Descriptive Test Result"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -550,7 +555,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-16 13:35:24.811062",
|
||||
"modified": "2020-07-30 18:18:38.516215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Lab Test",
|
||||
|
@ -6,23 +6,24 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
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):
|
||||
def validate(self):
|
||||
if not self.is_new():
|
||||
self.set_secondary_uom_result()
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_result_values()
|
||||
self.db_set('submitted_date', getdate())
|
||||
self.db_set('status', 'Completed')
|
||||
insert_lab_test_to_medical_record(self)
|
||||
|
||||
def on_cancel(self):
|
||||
delete_lab_test_from_medical_record(self)
|
||||
self.db_set('status', 'Cancelled')
|
||||
delete_lab_test_from_medical_record(self)
|
||||
self.reload()
|
||||
|
||||
def validate(self):
|
||||
if not self.is_new():
|
||||
self.set_secondary_uom_result()
|
||||
|
||||
def on_update(self):
|
||||
if self.sensitivity_test_items:
|
||||
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)
|
||||
except:
|
||||
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):
|
||||
@ -89,7 +103,7 @@ def create_multiple(doctype, docname):
|
||||
lab_test_created = create_lab_test_from_encounter(docname)
|
||||
|
||||
if lab_test_created:
|
||||
frappe.msgprint(_('Lab Test(s) {0} created'.format(lab_test_created)))
|
||||
frappe.msgprint(_('Lab Test(s) {0} created successfully').format(lab_test_created), indicator='green')
|
||||
else:
|
||||
frappe.msgprint(_('No Lab Tests created'))
|
||||
|
||||
@ -211,8 +225,9 @@ def create_sample_doc(template, patient, invoice, company = None):
|
||||
'docstatus': 0,
|
||||
'sample': template.sample
|
||||
})
|
||||
|
||||
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])
|
||||
quantity = int(sample_collection.sample_qty) + int(template.sample_qty)
|
||||
if template.sample_details:
|
||||
@ -238,7 +253,7 @@ def create_sample_doc(template, patient, invoice, company = None):
|
||||
sample_collection.company = company
|
||||
|
||||
if template.sample_details:
|
||||
sample_collection.sample_details = 'Test :' + (template.get('lab_test_name') or template.get('template')) + '\n' + 'Collection Detials:\n\t' + template.sample_details
|
||||
sample_collection.sample_details = _('Test :') + (template.get('lab_test_name') or template.get('template')) + '\n' + 'Collection Detials:\n\t' + template.sample_details
|
||||
sample_collection.save(ignore_permissions=True)
|
||||
|
||||
return sample_collection
|
||||
@ -248,26 +263,31 @@ def create_sample_collection(lab_test, template, patient, invoice):
|
||||
sample_collection = create_sample_doc(template, patient, invoice, lab_test.company)
|
||||
if sample_collection:
|
||||
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
|
||||
|
||||
def load_result_format(lab_test, template, prescription, invoice):
|
||||
if template.lab_test_template_type == 'Single':
|
||||
create_normals(template, lab_test)
|
||||
|
||||
elif template.lab_test_template_type == 'Compound':
|
||||
create_compounds(template, lab_test, False)
|
||||
|
||||
elif template.lab_test_template_type == 'Descriptive':
|
||||
create_descriptives(template, lab_test)
|
||||
|
||||
elif template.lab_test_template_type == 'Grouped':
|
||||
# Iterate for each template in the group and create one result for all.
|
||||
for lab_test_group in template.lab_test_groups:
|
||||
# Template_in_group = None
|
||||
if lab_test_group.lab_test_template:
|
||||
template_in_group = frappe.get_doc('Lab Test Template',
|
||||
lab_test_group.lab_test_template)
|
||||
template_in_group = frappe.get_doc('Lab Test Template', lab_test_group.lab_test_template)
|
||||
if template_in_group:
|
||||
if template_in_group.lab_test_template_type == 'Single':
|
||||
create_normals(template_in_group, lab_test)
|
||||
|
||||
elif template_in_group.lab_test_template_type == 'Compound':
|
||||
normal_heading = lab_test.append('normal_test_items')
|
||||
normal_heading.lab_test_name = template_in_group.lab_test_name
|
||||
@ -275,6 +295,7 @@ def load_result_format(lab_test, template, prescription, invoice):
|
||||
normal_heading.allow_blank = 1
|
||||
normal_heading.template = template_in_group.name
|
||||
create_compounds(template_in_group, lab_test, True)
|
||||
|
||||
elif template_in_group.lab_test_template_type == 'Descriptive':
|
||||
descriptive_heading = lab_test.append('descriptive_test_items')
|
||||
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.template = template_in_group.name
|
||||
create_descriptives(template_in_group, lab_test)
|
||||
|
||||
else: # Lab Test Group - Add New Line
|
||||
normal = lab_test.append('normal_test_items')
|
||||
normal.lab_test_name = lab_test_group.group_event
|
||||
@ -292,6 +314,7 @@ def load_result_format(lab_test, template, prescription, invoice):
|
||||
normal.allow_blank = lab_test_group.allow_blank
|
||||
normal.require_result_value = 1
|
||||
normal.template = template.name
|
||||
|
||||
if template.lab_test_template_type != 'No Result':
|
||||
if prescription:
|
||||
lab_test.prescription = prescription
|
||||
@ -302,9 +325,10 @@ def load_result_format(lab_test, template, prescription, invoice):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_employee_by_user_id(user_id):
|
||||
emp_id = frappe.db.get_value('Employee', { 'user_id': user_id })
|
||||
employee = frappe.get_doc('Employee', emp_id)
|
||||
return employee
|
||||
emp_id = frappe.db.exists('Employee', { 'user_id': user_id })
|
||||
if emp_id:
|
||||
return frappe.get_doc('Employee', emp_id)
|
||||
return None
|
||||
|
||||
def insert_lab_test_to_medical_record(doc):
|
||||
table_row = False
|
||||
|
@ -3,13 +3,16 @@
|
||||
*/
|
||||
frappe.listview_settings['Lab Test'] = {
|
||||
add_fields: ['name', 'status', 'invoiced'],
|
||||
filters: [['docstatus', '=', '0']],
|
||||
filters: [['docstatus', '=', '1']],
|
||||
get_indicator: function (doc) {
|
||||
if (doc.status == 'Approved') {
|
||||
if (doc.status === 'Approved') {
|
||||
return [__('Approved'), 'green', 'status, =, Approved'];
|
||||
}
|
||||
if (doc.status == 'Rejected') {
|
||||
} else if (doc.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) {
|
||||
@ -21,7 +24,7 @@ frappe.listview_settings['Lab Test'] = {
|
||||
|
||||
var create_multiple_dialog = function (listview) {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: 'Create Multiple Lab Test',
|
||||
title: 'Create Multiple Lab Tests',
|
||||
width: 100,
|
||||
fields: [
|
||||
{ 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 () {
|
||||
frappe.call({
|
||||
method: 'erpnext.healthcare.doctype.lab_test.lab_test.create_multiple',
|
||||
|
@ -3,8 +3,204 @@
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
import unittest
|
||||
|
||||
# test_records = frappe.get_test_records('Lab Test')
|
||||
import frappe
|
||||
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):
|
||||
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
|
||||
|
@ -93,7 +93,8 @@
|
||||
"depends_on": "secondary_uom",
|
||||
"fieldname": "conversion_factor",
|
||||
"fieldtype": "Float",
|
||||
"label": "Conversion Factor"
|
||||
"label": "Conversion Factor",
|
||||
"mandatory_depends_on": "secondary_uom"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -106,7 +107,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-24 10:59:01.921924",
|
||||
"modified": "2020-07-30 12:36:03.082391",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Lab Test Group Template",
|
||||
|
@ -34,14 +34,15 @@
|
||||
"descriptive_test_templates",
|
||||
"section_break_group",
|
||||
"lab_test_groups",
|
||||
"medical_coding_section",
|
||||
"medical_code_standard",
|
||||
"medical_code",
|
||||
"sb_sample_collection",
|
||||
"sample",
|
||||
"sample_uom",
|
||||
"sample_qty",
|
||||
"column_break_33",
|
||||
"sample_details",
|
||||
"medical_coding_section",
|
||||
"medical_code",
|
||||
"medical_code_standard",
|
||||
"worksheet_section",
|
||||
"worksheet_instructions",
|
||||
"result_legend_section",
|
||||
@ -112,7 +113,7 @@
|
||||
{
|
||||
"default": "1",
|
||||
"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",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Billable",
|
||||
@ -128,6 +129,7 @@
|
||||
"mandatory_depends_on": "eval:doc.is_billable == 1"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "medical_coding_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Medical Coding"
|
||||
@ -184,7 +186,7 @@
|
||||
"depends_on": "eval:doc.lab_test_template_type == 'Descriptive'",
|
||||
"fieldname": "section_break_special",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Descriptive"
|
||||
"label": "Descriptive Test"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -196,7 +198,7 @@
|
||||
"depends_on": "eval:doc.lab_test_template_type == 'Grouped'",
|
||||
"fieldname": "section_break_group",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Group"
|
||||
"label": "Group Tests"
|
||||
},
|
||||
{
|
||||
"fieldname": "lab_test_groups",
|
||||
@ -217,7 +219,6 @@
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "sb_sample_collection",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Sample Collection"
|
||||
@ -311,10 +312,14 @@
|
||||
"fieldname": "descriptive_test_templates",
|
||||
"fieldtype": "Table",
|
||||
"options": "Descriptive Test Template"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_33",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-07-13 12:57:09.925436",
|
||||
"modified": "2020-07-30 14:32:40.449818",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Lab Test Template",
|
||||
|
@ -15,7 +15,8 @@ class LabTestTemplate(Document):
|
||||
|
||||
def validate(self):
|
||||
if self.is_billable and (not self.lab_test_rate or self.lab_test_rate <= 0.0):
|
||||
frappe.throw(_("Standard Selling Rate should be greater than zero."))
|
||||
frappe.throw(_('Standard Selling Rate should be greater than zero.'))
|
||||
|
||||
self.validate_conversion_factor()
|
||||
self.enable_disable_item()
|
||||
|
||||
@ -42,7 +43,9 @@ class LabTestTemplate(Document):
|
||||
# Remove template reference from item and disable item
|
||||
if self.item:
|
||||
try:
|
||||
frappe.delete_doc('Item', self.item)
|
||||
item = self.item
|
||||
self.db_set('item', '')
|
||||
frappe.delete_doc('Item', item)
|
||||
except Exception:
|
||||
frappe.throw(_('Not permitted. Please disable the Lab Test Template'))
|
||||
|
||||
@ -63,26 +66,26 @@ class LabTestTemplate(Document):
|
||||
'standard_rate': self.lab_test_rate,
|
||||
'description': self.lab_test_description
|
||||
})
|
||||
item.save()
|
||||
item.flags.ignore_mandatory = True
|
||||
item.save(ignore_permissions=True)
|
||||
|
||||
def item_price_exists(self):
|
||||
item_price = frappe.db.exists({'doctype': 'Item Price', 'item_code': self.lab_test_code})
|
||||
if item_price:
|
||||
return item_price[0][0]
|
||||
else:
|
||||
return False
|
||||
|
||||
def validate_conversion_factor(self):
|
||||
if self.lab_test_template_type == "Single" and self.secondary_uom and not self.conversion_factor:
|
||||
frappe.throw(_("Conversion Factor is mandatory"))
|
||||
if self.lab_test_template_type == "Compound":
|
||||
if self.lab_test_template_type == 'Single' and self.secondary_uom and not self.conversion_factor:
|
||||
frappe.throw(_('Conversion Factor is mandatory'))
|
||||
if self.lab_test_template_type == 'Compound':
|
||||
for item in self.normal_test_templates:
|
||||
if item.secondary_uom and not item.conversion_factor:
|
||||
frappe.throw(_("Conversion Factor is mandatory"))
|
||||
if self.lab_test_template_type == "Grouped":
|
||||
frappe.throw(_('Row #{0}: Conversion Factor is mandatory').format(item.idx))
|
||||
if self.lab_test_template_type == 'Grouped':
|
||||
for group in self.lab_test_groups:
|
||||
if group.template_or_new_line == "Add New Line" and group.secondary_uom and not group.conversion_factor:
|
||||
frappe.throw(_("Conversion Factor is mandatory"))
|
||||
if group.template_or_new_line == 'Add New Line' and group.secondary_uom and not group.conversion_factor:
|
||||
frappe.throw(_('Row #{0}: Conversion Factor is mandatory').format(group.idx))
|
||||
|
||||
|
||||
def create_item_from_template(doc):
|
||||
|
@ -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']
|
||||
}
|
||||
]
|
||||
}
|
@ -3,5 +3,5 @@
|
||||
*/
|
||||
frappe.listview_settings['Lab Test Template'] = {
|
||||
add_fields: ['lab_test_name', 'lab_test_code', 'lab_test_rate'],
|
||||
filters: [['disabled', '=', 0]]
|
||||
filters: [['disabled', '=', 'No']]
|
||||
};
|
||||
|
@ -226,7 +226,9 @@ let check_and_set_availability = function(frm) {
|
||||
primary_action_label: __('Book'),
|
||||
primary_action: function() {
|
||||
frm.set_value('appointment_time', selected_slot);
|
||||
if (!frm.doc.duration) {
|
||||
frm.set_value('duration', duration);
|
||||
}
|
||||
frm.set_value('practitioner', d.get_value('practitioner'));
|
||||
frm.set_value('department', d.get_value('department'));
|
||||
frm.set_value('appointment_date', d.get_value('appointment_date'));
|
||||
|
@ -34,7 +34,7 @@ class TestPatientMedicalRecord(unittest.TestCase):
|
||||
self.assertTrue(medical_rec)
|
||||
|
||||
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
|
||||
medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': lab_test.name})
|
||||
self.assertTrue(medical_rec)
|
||||
@ -66,7 +66,7 @@ def create_vital_signs(appointment):
|
||||
|
||||
def create_lab_test_template(medical_department):
|
||||
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.lab_test_name = 'Blood Test'
|
||||
@ -76,7 +76,7 @@ def create_lab_test_template(medical_department):
|
||||
template.is_billable = 1
|
||||
template.lab_test_rate = 2000
|
||||
template.save()
|
||||
return template.name
|
||||
return template
|
||||
|
||||
def create_lab_test(template, patient):
|
||||
lab_test = frappe.new_doc('Lab Test')
|
||||
|
@ -3,19 +3,19 @@
|
||||
|
||||
frappe.ui.form.on('Sample Collection', {
|
||||
refresh: function(frm) {
|
||||
if(frappe.defaults.get_default("create_sample_collection_for_lab_test")){
|
||||
frm.add_custom_button(__("View Lab Tests"), function() {
|
||||
frappe.route_options = {"sample": frm.doc.name};
|
||||
frappe.set_route("List", "Lab Test");
|
||||
if (frappe.defaults.get_default('create_sample_collection_for_lab_test')) {
|
||||
frm.add_custom_button(__('View Lab Tests'), function() {
|
||||
frappe.route_options = {'sample': frm.doc.name};
|
||||
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){
|
||||
frappe.call({
|
||||
"method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail",
|
||||
'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
|
||||
args: {
|
||||
patient: frm.doc.patient
|
||||
},
|
||||
@ -24,8 +24,8 @@ frappe.ui.form.on("Sample Collection", "patient", function(frm) {
|
||||
if (data.message.dob){
|
||||
age = calculate_age(data.message.dob);
|
||||
}
|
||||
frappe.model.set_value(frm.doctype,frm.docname, "patient_age", age);
|
||||
frappe.model.set_value(frm.doctype,frm.docname, "patient_sex", data.message.sex);
|
||||
frappe.model.set_value(frm.doctype,frm.docname, 'patient_age', age);
|
||||
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();
|
||||
age.setTime(ageMS);
|
||||
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)';
|
||||
};
|
||||
|
@ -9,8 +9,10 @@
|
||||
"document_type": "Document",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"patient_details_section",
|
||||
"naming_series",
|
||||
"patient",
|
||||
"patient_name",
|
||||
"patient_age",
|
||||
"patient_sex",
|
||||
"column_break_4",
|
||||
@ -25,15 +27,17 @@
|
||||
"collected_by",
|
||||
"collected_time",
|
||||
"num_print",
|
||||
"amended_from",
|
||||
"section_break_15",
|
||||
"sample_details"
|
||||
"sample_details",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fetch_from": "patient.inpatient_record",
|
||||
"fieldname": "inpatient_record",
|
||||
"fieldtype": "Link",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Inpatient Record",
|
||||
"options": "Inpatient Record",
|
||||
"read_only": 1
|
||||
@ -42,6 +46,8 @@
|
||||
"bold": 1,
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Series",
|
||||
"no_copy": 1,
|
||||
"options": "HLC-SC-.YYYY.-",
|
||||
@ -52,6 +58,8 @@
|
||||
"default": "0",
|
||||
"fieldname": "invoiced",
|
||||
"fieldtype": "Check",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Invoiced",
|
||||
"no_copy": 1,
|
||||
"read_only": 1,
|
||||
@ -61,41 +69,60 @@
|
||||
"fetch_from": "inpatient_record.patient",
|
||||
"fieldname": "patient",
|
||||
"fieldtype": "Link",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"ignore_user_permissions": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Patient",
|
||||
"options": "Patient",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldtype": "Column Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "patient_age",
|
||||
"fieldtype": "Data",
|
||||
"label": "Age"
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Age",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "patient.sex",
|
||||
"fieldname": "patient_sex",
|
||||
"fieldtype": "Data",
|
||||
"label": "Gender"
|
||||
"fieldtype": "Link",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Gender",
|
||||
"options": "Gender",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Sample Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "sample",
|
||||
"fieldtype": "Link",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
@ -108,16 +135,23 @@
|
||||
"fetch_from": "sample.sample_uom",
|
||||
"fieldname": "sample_uom",
|
||||
"fieldtype": "Data",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "UOM"
|
||||
"label": "UOM",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_10",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldtype": "Column Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "collected_by",
|
||||
"fieldtype": "Link",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Collected By",
|
||||
"options": "User"
|
||||
@ -125,20 +159,27 @@
|
||||
{
|
||||
"fieldname": "collected_time",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Collected Time"
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Collected On"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "1",
|
||||
"description": "Number of prints required for labelling the samples",
|
||||
"fieldname": "num_print",
|
||||
"fieldtype": "Int",
|
||||
"label": "No. of print",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "No. of prints",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Sample Collection",
|
||||
@ -147,25 +188,43 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "sample_qty",
|
||||
"fieldtype": "Float",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Quantity"
|
||||
},
|
||||
{
|
||||
"fieldname": "sample_details",
|
||||
"fieldtype": "Long Text",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"ignore_xss_filter": 1,
|
||||
"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,
|
||||
"links": [],
|
||||
"modified": "2020-05-25 14:36:46.990469",
|
||||
"modified": "2020-07-30 16:53:13.076104",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Sample Collection",
|
||||
|
@ -3,7 +3,12 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt
|
||||
from frappe import _
|
||||
|
||||
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'))
|
||||
|
@ -7,14 +7,28 @@ frappe.query_reports["Lab Test Report"] = {
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.now_date(),
|
||||
"width": "80"
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.now_date()
|
||||
"default": frappe.datetime.now_date(),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"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",
|
||||
@ -27,6 +41,17 @@ frappe.query_reports["Lab Test Report"] = {
|
||||
"label": __("Medical Department"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Medical Department"
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"label": __("Status"),
|
||||
"fieldtype": "Select",
|
||||
"options": "\nCompleted\nApproved\nRejected"
|
||||
},
|
||||
{
|
||||
"fieldname": "invoiced",
|
||||
"label": __("Invoiced"),
|
||||
"fieldtype": "Check"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -1,12 +1,13 @@
|
||||
{
|
||||
"add_total_row": 1,
|
||||
"add_total_row": 0,
|
||||
"creation": "2013-04-23 18:15:29",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 1,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2018-08-06 11:41:50.218737",
|
||||
"modified": "2020-07-30 18:53:20.102873",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Lab Test Report",
|
||||
|
@ -8,51 +8,204 @@ from frappe import msgprint, _
|
||||
def execute(filters=None):
|
||||
if not filters: filters = {}
|
||||
|
||||
lab_test_list = get_lab_test(filters)
|
||||
data, columns = [], []
|
||||
|
||||
columns = get_columns()
|
||||
lab_test_list = get_lab_tests(filters)
|
||||
|
||||
if not lab_test_list:
|
||||
msgprint(_("No record found"))
|
||||
msgprint(_('No records found'))
|
||||
return columns, lab_test_list
|
||||
|
||||
data = []
|
||||
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)
|
||||
|
||||
return columns, data
|
||||
chart = get_chart_data(data)
|
||||
report_summary = get_report_summary(data)
|
||||
return columns, data, None, chart, report_summary
|
||||
|
||||
|
||||
def get_columns():
|
||||
columns = [
|
||||
_("Test") + ":Data:120",
|
||||
_("Patient") + ":Link/Patient:180",
|
||||
_("Healthcare Practitioner") + ":Link/Healthcare Practitioner:120",
|
||||
_("Invoiced") + ":Check:100",
|
||||
_("Status") + ":Data:120",
|
||||
_("Result Date") + ":Date:120",
|
||||
_("Department") + ":Data:120",
|
||||
return [
|
||||
{
|
||||
'fieldname': 'test',
|
||||
'label': _('Lab Test'),
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Lab Test',
|
||||
'width': '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):
|
||||
conditions = ""
|
||||
conditions = {
|
||||
'docstatus': ('=', 1)
|
||||
}
|
||||
|
||||
if filters.get("patient"):
|
||||
conditions += "and patient = %(patient)s"
|
||||
if filters.get("from_date"):
|
||||
conditions += "and result_date >= %(from_date)s"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and result_date <= %(to_date)s"
|
||||
if filters.get("department"):
|
||||
conditions += " and department = %(department)s"
|
||||
if filters.get('from_date') and filters.get('to_date'):
|
||||
conditions['result_date'] = ('between', (filters.get('from_date'), filters.get('to_date')))
|
||||
filters.pop('from_date')
|
||||
filters.pop('to_date')
|
||||
|
||||
for key, value in filters.items():
|
||||
if filters.get(key):
|
||||
conditions[key] = value
|
||||
|
||||
return conditions
|
||||
|
||||
def get_lab_test(filters):
|
||||
conditions = get_conditions(filters)
|
||||
return frappe.db.sql("""select name, patient, lab_test_name, patient_name, status, result_date, practitioner, invoiced, department
|
||||
from `tabLab Test`
|
||||
where docstatus<2 %s order by submitted_date desc, name desc""" %
|
||||
conditions, filters, as_dict=1)
|
||||
def get_chart_data(data):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
labels = ['Completed', 'Approved', 'Rejected']
|
||||
|
||||
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',
|
||||
}
|
||||
]
|
||||
|
@ -98,7 +98,8 @@ def add_attendance(events, start, end, conditions=None):
|
||||
e = {
|
||||
"name": d.name,
|
||||
"doctype": "Attendance",
|
||||
"date": d.attendance_date,
|
||||
"start": d.attendance_date,
|
||||
"end": d.attendance_date,
|
||||
"title": cstr(d.status),
|
||||
"docstatus": d.docstatus
|
||||
}
|
||||
|
@ -1,12 +1,6 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.views.calendar["Attendance"] = {
|
||||
field_map: {
|
||||
"start": "attendance_date",
|
||||
"end": "attendance_date",
|
||||
"id": "name",
|
||||
"docstatus": 1
|
||||
},
|
||||
options: {
|
||||
header: {
|
||||
left: 'prev,next today',
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cint
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe import _
|
||||
@ -24,8 +25,7 @@ class JobOffer(Document):
|
||||
check_vacancies = frappe.get_single("HR Settings").check_vacancies
|
||||
if staffing_plan and check_vacancies:
|
||||
job_offers = self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date)
|
||||
|
||||
if not staffing_plan.get("vacancies") or staffing_plan.vacancies - len(job_offers) <= 0:
|
||||
if not staffing_plan.get("vacancies") or cint(staffing_plan.vacancies) - len(job_offers) <= 0:
|
||||
error_variable = 'for ' + frappe.bold(self.designation)
|
||||
if staffing_plan.get("parent"):
|
||||
error_variable = frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent))
|
||||
@ -65,7 +65,7 @@ def get_staffing_plan_detail(designation, company, offer_date):
|
||||
AND %s between sp.from_date and sp.to_date
|
||||
""", (designation, company, offer_date), as_dict=1)
|
||||
|
||||
return frappe._dict(detail[0]) if detail else None
|
||||
return frappe._dict(detail[0]) if (detail and detail[0].parent) else None
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_employee(source_name, target_doc=None):
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2019-05-09 15:47:39.760406",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
@ -54,6 +53,7 @@
|
||||
{
|
||||
"fieldname": "transaction_type",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Transaction Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
@ -109,9 +109,9 @@
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-27 14:40:10.502605",
|
||||
"modified": "2020-09-04 12:16:36.569066",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Ledger Entry",
|
||||
|
@ -0,0 +1,13 @@
|
||||
frappe.listview_settings['Leave Ledger Entry'] = {
|
||||
onload: function(listview) {
|
||||
if(listview.page.fields_dict.transaction_type) {
|
||||
listview.page.fields_dict.transaction_type.get_query = function() {
|
||||
return {
|
||||
"filters": {
|
||||
"name": ["in", ["Leave Allocation", "Leave Application", "Leave Encashment"]],
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
@ -103,7 +103,7 @@ def add_assignments(events, start, end, conditions=None):
|
||||
"doctype": "Shift Assignment",
|
||||
"start_date": d.start_date,
|
||||
"end_date": d.end_date if d.end_date else nowdate(),
|
||||
"title": cstr(d.employee_name) + \
|
||||
"title": cstr(d.employee_name) + ": "+ \
|
||||
cstr(d.shift_type),
|
||||
"docstatus": d.docstatus
|
||||
}
|
||||
|
@ -132,6 +132,9 @@ def get_conditions(filters):
|
||||
if filters.get('employee'):
|
||||
conditions['name'] = filters.get('employee')
|
||||
|
||||
if filters.get('company'):
|
||||
conditions['company'] = filters.get('company')
|
||||
|
||||
return conditions
|
||||
|
||||
def get_department_leave_approver_map(department=None):
|
||||
|
@ -73,8 +73,8 @@ frappe.ui.form.on('Loan', {
|
||||
|
||||
loan_type: function(frm) {
|
||||
frm.toggle_reqd("repayment_method", frm.doc.is_term_loan);
|
||||
frm.toggle_display("repayment_method", 1 - frm.doc.is_term_loan);
|
||||
frm.toggle_display("repayment_periods", s1 - frm.doc.is_term_loan);
|
||||
frm.toggle_display("repayment_method", frm.doc.is_term_loan);
|
||||
frm.toggle_display("repayment_periods", frm.doc.is_term_loan);
|
||||
},
|
||||
|
||||
|
||||
@ -119,12 +119,10 @@ frappe.ui.form.on('Loan', {
|
||||
|
||||
create_loan_security_unpledge: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.loan_management.doctype.loan.loan.create_loan_security_unpledge",
|
||||
method: "erpnext.loan_management.doctype.loan.loan.unpledge_security",
|
||||
args : {
|
||||
"loan": frm.doc.name,
|
||||
"applicant_type": frm.doc.applicant_type,
|
||||
"applicant": frm.doc.applicant,
|
||||
"company": frm.doc.company
|
||||
"as_dict": 1
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message)
|
||||
|
@ -7,7 +7,7 @@ import frappe, math, json
|
||||
import erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime
|
||||
|
||||
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
|
||||
class Loan(AccountsController):
|
||||
@ -223,29 +223,55 @@ def make_repayment_entry(loan, applicant_type, applicant, loan_type, company, as
|
||||
return repayment_entry
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_dict=1):
|
||||
loan_security_pledge_details = frappe.db.sql("""
|
||||
SELECT p.loan_security, sum(p.qty) as qty
|
||||
FROM `tabLoan Security Pledge` lsp , `tabPledge` p
|
||||
WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1
|
||||
GROUP BY p.loan_security
|
||||
""",(loan), as_dict=1)
|
||||
def unpledge_security(loan=None, loan_security_pledge=None, as_dict=0, save=0, submit=0, approve=0):
|
||||
# if loan is passed it will be considered as full unpledge
|
||||
if loan:
|
||||
pledge_qty_map = get_pledged_security_qty(loan)
|
||||
loan_doc = frappe.get_doc('Loan', loan)
|
||||
unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company,
|
||||
loan_doc.applicant_type, loan_doc.applicant)
|
||||
# will unpledge qty based on loan security pledge
|
||||
elif loan_security_pledge:
|
||||
security_map = {}
|
||||
pledge_doc = frappe.get_doc('Loan Security Pledge', loan_security_pledge)
|
||||
for security in pledge_doc.securities:
|
||||
security_map.setdefault(security.loan_security, security.qty)
|
||||
|
||||
unpledge_request = create_loan_security_unpledge(security_map, pledge_doc.loan,
|
||||
pledge_doc.company, pledge_doc.applicant_type, pledge_doc.applicant)
|
||||
|
||||
if save:
|
||||
unpledge_request.save()
|
||||
|
||||
if submit:
|
||||
unpledge_request.submit()
|
||||
|
||||
if approve:
|
||||
if unpledge_request.docstatus == 1:
|
||||
unpledge_request.status = 'Approved'
|
||||
unpledge_request.save()
|
||||
else:
|
||||
frappe.throw(_('Only submittted unpledge requests can be approved'))
|
||||
|
||||
if as_dict:
|
||||
return unpledge_request
|
||||
else:
|
||||
return unpledge_request
|
||||
|
||||
def create_loan_security_unpledge(unpledge_map, loan, company, applicant_type, applicant):
|
||||
unpledge_request = frappe.new_doc("Loan Security Unpledge")
|
||||
unpledge_request.applicant_type = applicant_type
|
||||
unpledge_request.applicant = applicant
|
||||
unpledge_request.loan = loan
|
||||
unpledge_request.company = company
|
||||
|
||||
for loan_security in loan_security_pledge_details:
|
||||
for security, qty in unpledge_map.items():
|
||||
if qty:
|
||||
unpledge_request.append('securities', {
|
||||
"loan_security": loan_security.loan_security,
|
||||
"qty": loan_security.qty
|
||||
"loan_security": security,
|
||||
"qty": qty
|
||||
})
|
||||
|
||||
if as_dict:
|
||||
return unpledge_request.as_dict()
|
||||
else:
|
||||
return unpledge_request
|
||||
|
||||
|
||||
|
@ -14,9 +14,11 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_
|
||||
process_loan_interest_accrual_for_term_loans)
|
||||
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
|
||||
from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall
|
||||
from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
|
||||
from erpnext.loan_management.doctype.loan.loan import unpledge_security
|
||||
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
||||
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
|
||||
from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount
|
||||
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts
|
||||
|
||||
class TestLoan(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -193,18 +195,13 @@ class TestLoan(unittest.TestCase):
|
||||
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
|
||||
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
|
||||
|
||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
|
||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6),
|
||||
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
||||
repayment_entry.submit()
|
||||
|
||||
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||
'paid_principal_amount'])
|
||||
amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)'])
|
||||
|
||||
unaccrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 6) \
|
||||
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||
|
||||
self.assertEquals(flt(amounts[0] + unaccrued_interest_amount, 3),
|
||||
flt(accrued_interest_amount, 3))
|
||||
self.assertEquals(flt(amount, 2),flt(accrued_interest_amount, 2))
|
||||
self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
|
||||
|
||||
loan.load_from_db()
|
||||
@ -306,13 +303,10 @@ class TestLoan(unittest.TestCase):
|
||||
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
||||
repayment_entry.submit()
|
||||
|
||||
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||
'paid_principal_amount'])
|
||||
|
||||
loan.load_from_db()
|
||||
self.assertEquals(loan.status, "Loan Closure Requested")
|
||||
|
||||
unpledge_request = create_loan_security_unpledge(loan.name, loan.applicant_type, loan.applicant, loan.company, as_dict=0)
|
||||
unpledge_request = unpledge_security(loan=loan.name, save=1)
|
||||
unpledge_request.submit()
|
||||
unpledge_request.status = 'Approved'
|
||||
unpledge_request.save()
|
||||
@ -323,6 +317,102 @@ class TestLoan(unittest.TestCase):
|
||||
self.assertEqual(loan.status, 'Closed')
|
||||
self.assertEquals(sum(pledged_qty.values()), 0)
|
||||
|
||||
amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment")
|
||||
self.assertEqual(amounts['pending_principal_amount'], 0)
|
||||
self.assertEqual(amounts['payable_principal_amount'], 0)
|
||||
self.assertEqual(amounts['interest_amount'], 0)
|
||||
|
||||
def test_disbursal_check_with_shortfall(self):
|
||||
pledges = [{
|
||||
"loan_security": "Test Security 2",
|
||||
"qty": 8000.00,
|
||||
"haircut": 50,
|
||||
}]
|
||||
|
||||
loan_application = create_loan_application('_Test Company', self.applicant2,
|
||||
'Stock Loan', pledges, "Repay Over Number of Periods", 12)
|
||||
|
||||
create_pledge(loan_application)
|
||||
|
||||
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
|
||||
loan.submit()
|
||||
|
||||
#Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
|
||||
make_loan_disbursement_entry(loan.name, 700000)
|
||||
|
||||
frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
|
||||
where loan_security='Test Security 2'""")
|
||||
|
||||
create_process_loan_security_shortfall()
|
||||
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
|
||||
self.assertTrue(loan_security_shortfall)
|
||||
|
||||
self.assertEqual(get_disbursal_amount(loan.name), 0)
|
||||
|
||||
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
|
||||
where loan_security='Test Security 2'""")
|
||||
|
||||
def test_disbursal_check_without_shortfall(self):
|
||||
pledges = [{
|
||||
"loan_security": "Test Security 2",
|
||||
"qty": 8000.00,
|
||||
"haircut": 50,
|
||||
}]
|
||||
|
||||
loan_application = create_loan_application('_Test Company', self.applicant2,
|
||||
'Stock Loan', pledges, "Repay Over Number of Periods", 12)
|
||||
|
||||
create_pledge(loan_application)
|
||||
|
||||
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
|
||||
loan.submit()
|
||||
|
||||
#Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
|
||||
make_loan_disbursement_entry(loan.name, 700000)
|
||||
|
||||
self.assertEqual(get_disbursal_amount(loan.name), 300000)
|
||||
|
||||
def test_pending_loan_amount_after_closure_request(self):
|
||||
pledge = [{
|
||||
"loan_security": "Test Security 1",
|
||||
"qty": 4000.00
|
||||
}]
|
||||
|
||||
loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
|
||||
create_pledge(loan_application)
|
||||
|
||||
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
|
||||
loan.submit()
|
||||
|
||||
self.assertEquals(loan.loan_amount, 1000000)
|
||||
|
||||
first_date = '2019-10-01'
|
||||
last_date = '2019-10-30'
|
||||
|
||||
no_of_days = date_diff(last_date, first_date) + 1
|
||||
|
||||
no_of_days += 6
|
||||
|
||||
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
||||
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||
|
||||
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
|
||||
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
|
||||
|
||||
amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment")
|
||||
|
||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6),
|
||||
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
||||
repayment_entry.submit()
|
||||
|
||||
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||
'paid_principal_amount'])
|
||||
|
||||
loan.load_from_db()
|
||||
self.assertEquals(loan.status, "Loan Closure Requested")
|
||||
|
||||
amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment")
|
||||
self.assertEquals(amounts['pending_principal_amount'], 0.0)
|
||||
|
||||
def create_loan_accounts():
|
||||
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
|
||||
|
@ -33,18 +33,18 @@ frappe.ui.form.on('Loan Application', {
|
||||
|
||||
if (frm.doc.is_secured_loan) {
|
||||
frappe.db.get_value("Loan Security Pledge", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
|
||||
if (!r) {
|
||||
if (Object.keys(r).length === 0) {
|
||||
frm.add_custom_button(__('Loan Security Pledge'), function() {
|
||||
frm.trigger('create_loan_security_pledge')
|
||||
frm.trigger('create_loan_security_pledge');
|
||||
},__('Create'))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frappe.db.get_value("Loan", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
|
||||
if (!r) {
|
||||
if (Object.keys(r).length === 0) {
|
||||
frm.add_custom_button(__('Loan'), function() {
|
||||
frm.trigger('create_loan')
|
||||
frm.trigger('create_loan');
|
||||
},__('Create'))
|
||||
} else {
|
||||
frm.set_df_property('status', 'read_only', 1);
|
||||
@ -54,7 +54,7 @@ frappe.ui.form.on('Loan Application', {
|
||||
},
|
||||
create_loan: function(frm) {
|
||||
if (frm.doc.status != "Approved") {
|
||||
frappe.throw(__("Cannot create loan until application is approved"))
|
||||
frappe.throw(__("Cannot create loan until application is approved"));
|
||||
}
|
||||
|
||||
frappe.model.open_mapped_doc({
|
||||
|
@ -67,28 +67,10 @@ class LoanDisbursement(AccountsController):
|
||||
disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount
|
||||
total_payment = loan_details.total_payment
|
||||
|
||||
if disbursed_amount > loan_details.loan_amount and loan_details.is_term_loan:
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than loan amount"))
|
||||
possible_disbursal_amount = get_disbursal_amount(self.against_loan)
|
||||
|
||||
if loan_details.status == 'Disbursed':
|
||||
pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
|
||||
- flt(loan_details.total_principal_paid)
|
||||
else:
|
||||
pending_principal_amount = loan_details.disbursed_amount
|
||||
|
||||
security_value = 0.0
|
||||
if loan_details.is_secured_loan:
|
||||
security_value = get_total_pledged_security_value(self.against_loan)
|
||||
|
||||
if not security_value:
|
||||
security_value = loan_details.loan_amount
|
||||
|
||||
if pending_principal_amount + self.disbursed_amount > flt(security_value):
|
||||
allowed_amount = security_value - pending_principal_amount
|
||||
if allowed_amount < 0:
|
||||
allowed_amount = 0
|
||||
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(allowed_amount))
|
||||
if self.disbursed_amount > possible_disbursal_amount:
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(possible_disbursal_amount))
|
||||
|
||||
if loan_details.status == "Disbursed" and not loan_details.is_term_loan:
|
||||
process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1),
|
||||
@ -176,3 +158,32 @@ def get_total_pledged_security_value(loan):
|
||||
security_value += (loan_security_price_map.get(security) * qty * hair_cut_map.get(security))/100
|
||||
|
||||
return security_value
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_disbursal_amount(loan):
|
||||
loan_details = frappe.get_all("Loan", fields = ["loan_amount", "disbursed_amount", "total_payment",
|
||||
"total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan"],
|
||||
filters= { "name": loan })[0]
|
||||
|
||||
if loan_details.is_secured_loan and frappe.get_all('Loan Security Shortfall', filters={'loan': loan,
|
||||
'status': 'Pending'}):
|
||||
return 0
|
||||
|
||||
if loan_details.status == 'Disbursed':
|
||||
pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
|
||||
- flt(loan_details.total_principal_paid)
|
||||
else:
|
||||
pending_principal_amount = flt(loan_details.disbursed_amount)
|
||||
|
||||
security_value = 0.0
|
||||
if loan_details.is_secured_loan:
|
||||
security_value = get_total_pledged_security_value(loan)
|
||||
|
||||
if not security_value and not loan_details.is_secured_loan:
|
||||
security_value = flt(loan_details.loan_amount)
|
||||
|
||||
disbursal_amount = flt(security_value) - flt(pending_principal_amount)
|
||||
|
||||
return disbursal_amount
|
||||
|
||||
|
||||
|
@ -85,8 +85,11 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
||||
if no_of_days <= 0:
|
||||
return
|
||||
|
||||
if loan.status == 'Disbursed':
|
||||
pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
else:
|
||||
pending_principal_amount = loan.disbursed_amount
|
||||
|
||||
interest_per_day = (pending_principal_amount * loan.rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100)
|
||||
payable_interest = interest_per_day * no_of_days
|
||||
@ -107,7 +110,7 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
||||
|
||||
def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None):
|
||||
query_filters = {
|
||||
"status": "Disbursed",
|
||||
"status": ('in', ['Disbursed', 'Partially Disbursed']),
|
||||
"docstatus": 1
|
||||
}
|
||||
|
||||
@ -118,8 +121,9 @@ def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_inte
|
||||
|
||||
if not open_loans:
|
||||
open_loans = frappe.get_all("Loan",
|
||||
fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account", "is_term_loan",
|
||||
"disbursement_date", "applicant_type", "applicant", "rate_of_interest", "total_interest_payable", "repayment_start_date"],
|
||||
fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account",
|
||||
"is_term_loan", "status", "disbursement_date", "disbursed_amount", "applicant_type", "applicant",
|
||||
"rate_of_interest", "total_interest_payable", "total_principal_paid", "repayment_start_date"],
|
||||
filters=query_filters)
|
||||
|
||||
for loan in open_loans:
|
||||
@ -209,7 +213,8 @@ def get_last_accural_date_in_current_month(loan):
|
||||
WHERE loan = %s""", (loan.name))
|
||||
|
||||
if last_posting_date[0][0]:
|
||||
return last_posting_date[0][0]
|
||||
# interest for last interest accrual date is already booked, so add 1 day
|
||||
return add_days(last_posting_date[0][0], 1)
|
||||
else:
|
||||
return loan.disbursement_date
|
||||
|
||||
|
@ -13,6 +13,7 @@ from frappe.utils import date_diff, add_days, getdate, add_months, get_first_day
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import update_shortfall_status
|
||||
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans
|
||||
|
||||
class LoanRepayment(AccountsController):
|
||||
|
||||
@ -22,6 +23,9 @@ class LoanRepayment(AccountsController):
|
||||
self.validate_amount()
|
||||
self.allocate_amounts(amounts['pending_accrual_entries'])
|
||||
|
||||
def before_submit(self):
|
||||
self.book_unaccrued_interest()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_paid_amount()
|
||||
self.make_gl_entries()
|
||||
@ -72,6 +76,26 @@ class LoanRepayment(AccountsController):
|
||||
msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount)
|
||||
frappe.throw(msg)
|
||||
|
||||
def book_unaccrued_interest(self):
|
||||
if self.payment_type == 'Loan Closure':
|
||||
total_interest_paid = 0
|
||||
for payment in self.repayment_details:
|
||||
total_interest_paid += payment.paid_interest_amount
|
||||
|
||||
if total_interest_paid < self.interest_payable:
|
||||
if not self.is_term_loan:
|
||||
process = process_loan_interest_accrual_for_demand_loans(posting_date=self.posting_date,
|
||||
loan=self.against_loan)
|
||||
|
||||
lia = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual':
|
||||
process}, ['name', 'interest_amount', 'payable_principal_amount'], as_dict=1)
|
||||
|
||||
self.append('repayment_details', {
|
||||
'loan_interest_accrual': lia.name,
|
||||
'paid_interest_amount': lia.interest_amount,
|
||||
'paid_principal_amount': lia.payable_principal_amount
|
||||
})
|
||||
|
||||
def update_paid_amount(self):
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
|
||||
@ -116,6 +140,7 @@ class LoanRepayment(AccountsController):
|
||||
def allocate_amounts(self, paid_entries):
|
||||
self.set('repayment_details', [])
|
||||
self.principal_amount_paid = 0
|
||||
total_interest_paid = 0
|
||||
interest_paid = self.amount_paid - self.penalty_amount
|
||||
|
||||
if self.amount_paid - self.penalty_amount > 0 and paid_entries:
|
||||
@ -137,12 +162,17 @@ class LoanRepayment(AccountsController):
|
||||
interest_paid = 0
|
||||
paid_principal=0
|
||||
|
||||
total_interest_paid += interest_amount
|
||||
self.append('repayment_details', {
|
||||
'loan_interest_accrual': lia,
|
||||
'paid_interest_amount': interest_amount,
|
||||
'paid_principal_amount': paid_principal
|
||||
})
|
||||
|
||||
if self.payment_type == 'Loan Closure' and total_interest_paid < self.interest_payable:
|
||||
unaccrued_interest = self.interest_payable - total_interest_paid
|
||||
interest_paid -= unaccrued_interest
|
||||
|
||||
if interest_paid:
|
||||
self.principal_amount_paid += interest_paid
|
||||
|
||||
@ -297,7 +327,10 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
||||
if not final_due_date:
|
||||
final_due_date = add_days(due_date, loan_type_details.grace_period_in_days)
|
||||
|
||||
if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested', 'Closed'):
|
||||
pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
|
||||
else:
|
||||
pending_principal_amount = against_loan_doc.disbursed_amount
|
||||
|
||||
if payment_type == "Loan Closure":
|
||||
if due_date:
|
||||
|
@ -21,6 +21,10 @@
|
||||
"total_security_value",
|
||||
"column_break_11",
|
||||
"maximum_loan_value",
|
||||
"more_information_section",
|
||||
"reference_no",
|
||||
"column_break_18",
|
||||
"description",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
@ -129,11 +133,34 @@
|
||||
"label": "Applicant Type",
|
||||
"options": "Employee\nMember\nCustomer",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "more_information_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Information"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "reference_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference No"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Description"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-02 23:38:24.002382",
|
||||
"modified": "2020-09-04 22:38:19.894488",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Security Pledge",
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import get_datetime
|
||||
from frappe.utils import get_datetime, flt
|
||||
from frappe.model.document import Document
|
||||
from six import iteritems
|
||||
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
||||
@ -51,13 +51,19 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
|
||||
"valid_upto": (">=", update_time)
|
||||
}, as_list=1))
|
||||
|
||||
loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid'],
|
||||
filters={'status': 'Disbursed', 'is_secured_loan': 1})
|
||||
loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid', 'total_payment',
|
||||
'total_interest_payable', 'disbursed_amount', 'status'],
|
||||
filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1})
|
||||
|
||||
loan_security_map = {}
|
||||
|
||||
for loan in loans:
|
||||
outstanding_amount = loan.loan_amount - loan.total_principal_paid
|
||||
if loan.status == 'Disbursed':
|
||||
outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
else:
|
||||
outstanding_amount = loan.disbursed_amount
|
||||
|
||||
pledged_securities = get_pledged_security_qty(loan.name)
|
||||
ltv_ratio = ''
|
||||
security_value = 0.0
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user