Merge branch 'develop' of https://github.com/frappe/erpnext into develop
diff --git a/README.md b/README.md
index 80ebdb6..0f6a521 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@
ERPNext as a monolith includes the following areas for managing businesses:
1. [Accounting](https://erpnext.com/open-source-accounting)
-1. [Inventory](https://erpnext.com/distribution/inventory-management-system)
+1. [Warehouse Management](https://erpnext.com/distribution/warehouse-management-system)
1. [CRM](https://erpnext.com/open-source-crm)
1. [Sales](https://erpnext.com/open-source-sales-purchase)
1. [Purchase](https://erpnext.com/open-source-sales-purchase)
diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json
index 2c52314..3f23ba9 100644
--- a/erpnext/accounts/desk_page/accounting/accounting.json
+++ b/erpnext/accounts/desk_page/accounting/accounting.json
@@ -43,7 +43,7 @@
{
"hidden": 0,
"label": "Bank Statement",
- "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
+ "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@@ -98,7 +98,7 @@
"idx": 0,
"is_standard": 1,
"label": "Accounting",
- "modified": "2020-09-03 10:37:07.865801",
+ "modified": "2020-09-09 11:45:33.766400",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
@@ -148,11 +148,6 @@
"type": "Report"
},
{
- "label": "Point of Sale",
- "link_to": "point-of-sale",
- "type": "Page"
- },
- {
"label": "Dashboard",
"link_to": "Accounts",
"type": "Dashboard"
diff --git a/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.py b/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.py
index 5b6eb9d..27dd8e4 100644
--- a/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.py
+++ b/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.py
@@ -55,7 +55,7 @@
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 @@
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 @@
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"))
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index 0fe57c3..2754633 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -91,15 +91,11 @@
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 @@
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",
diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
index 856c371..8915f79 100644
--- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
+++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
@@ -38,8 +38,8 @@
"reqd": 1
}
],
- "modified": "2020-06-18 20:27:42.615842",
- "modified_by": "ahmad@havenir.com",
+ "modified": "2020-09-18 17:26:09.703215",
+ "modified_by": "Administrator",
"module": "Accounts",
"name": "Item Tax Template",
"owner": "Administrator",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 0a385d0..34c262e 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -1023,7 +1023,7 @@
return journal_entry.as_dict()
@frappe.whitelist()
-def make_reverse_journal_entry(source_name, target_doc=None, ignore_permissions=False):
+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):
@@ -1049,6 +1049,6 @@
},
"postprocess": update_accounts,
},
- }, target_doc, ignore_permissions=ignore_permissions)
+ }, target_doc)
return doclist
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json
index f3df1f0..50fc3bb 100644
--- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json
+++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json
@@ -45,11 +45,11 @@
],
"icon": "fa fa-credit-card",
"idx": 1,
- "modified": "2019-08-14 14:58:42.079115",
- "modified_by": "sammish.thundiyil@gmail.com",
+ "modified": "2020-09-18 17:26:09.703215",
+ "modified_by": "Administrator",
"module": "Accounts",
"name": "Mode of Payment",
- "owner": "harshada@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"create": 1,
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 9fc44bc..e117471 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -12,9 +12,10 @@
setup: function(frm) {
frm.set_query("paid_from", function() {
+ frm.events.validate_company(frm);
+
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
-
return {
filters: {
"account_type": ["in", account_types],
@@ -23,13 +24,16 @@
}
}
});
+
frm.set_query("party_type", function() {
+ frm.events.validate_company(frm);
return{
filters: {
"name": ["in", Object.keys(frappe.boot.party_account_types)],
}
}
});
+
frm.set_query("party_bank_account", function() {
return {
filters: {
@@ -39,6 +43,7 @@
}
}
});
+
frm.set_query("bank_account", function() {
return {
filters: {
@@ -47,6 +52,7 @@
}
}
});
+
frm.set_query("contact_person", function() {
if (frm.doc.party) {
return {
@@ -58,10 +64,12 @@
};
}
});
+
frm.set_query("paid_to", function() {
+ frm.events.validate_company(frm);
+
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
-
return {
filters: {
"account_type": ["in", account_types],
@@ -150,6 +158,12 @@
frm.events.show_general_ledger(frm);
},
+ validate_company: (frm) => {
+ if (!frm.doc.company){
+ frappe.throw({message:__("Please select a Company first."), title: __("Mandatory")});
+ }
+ },
+
company: function(frm) {
frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm);
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 9979377..72149a6 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2016-06-01 14:38:51.012597",
@@ -63,6 +64,7 @@
"cost_center",
"section_break_12",
"status",
+ "custom_remarks",
"remarks",
"column_break_16",
"letter_head",
@@ -462,7 +464,8 @@
"fieldname": "remarks",
"fieldtype": "Small Text",
"label": "Remarks",
- "no_copy": 1
+ "no_copy": 1,
+ "read_only_depends_on": "eval:doc.custom_remarks == 0"
},
{
"fieldname": "column_break_16",
@@ -573,10 +576,18 @@
"label": "Status",
"options": "\nDraft\nSubmitted\nCancelled",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "custom_remarks",
+ "fieldtype": "Check",
+ "label": "Custom Remarks"
}
],
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
- "modified": "2019-12-08 13:02:30.016610",
+ "links": [],
+ "modified": "2020-09-02 13:39:43.383705",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 842c64f..11ab020 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -453,7 +453,7 @@
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
def set_remarks(self):
- if self.remarks: return
+ if self.custom_remarks: return
if self.payment_type=="Internal Transfer":
remarks = [_("Amount {0} {1} transferred from {2} to {3}")
@@ -1172,30 +1172,23 @@
from frappe.model.mapper import get_mapped_doc
def set_missing_values(source, target):
target.payment_order_type = "Payment Entry"
+ 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,
+ ))
- 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
-
-
- doclist = get_mapped_doc("Payment Entry", source_name, {
+ 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)
diff --git a/erpnext/accounts/doctype/payment_order/payment_order.py b/erpnext/accounts/doctype/payment_order/payment_order.py
index e5880aa..8d29ae7 100644
--- a/erpnext/accounts/doctype/payment_order/payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/payment_order.py
@@ -21,10 +21,15 @@
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
diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py
index 711c4cc..1c23e2a 100644
--- a/erpnext/accounts/doctype/payment_order/test_payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py
@@ -5,6 +5,45 @@
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
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_order_reference/payment_order_reference.json b/erpnext/accounts/doctype/payment_order_reference/payment_order_reference.json
index db0b761..d94ba74 100644
--- a/erpnext/accounts/doctype/payment_order_reference/payment_order_reference.json
+++ b/erpnext/accounts/doctype/payment_order_reference/payment_order_reference.json
@@ -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",
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
index d04f25b..47546c0 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
@@ -291,11 +291,11 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-21 16:15:49.089450",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Period Closing Voucher",
- "owner": "jai@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"amend": 1,
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
index 8dcd2e4..9336fc3 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -55,14 +55,48 @@
},
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 @@
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_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_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,
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index 2a2e3df..4780688 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -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",
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index 9c62a87..514a2ac 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -182,8 +182,9 @@
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 @@
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()
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index 00dbad5..11b9d25 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -24,11 +24,20 @@
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 @@
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 = self.process_merging_into_sales_invoice(sales)
+ sales_invoice, credit_note = "", ""
+ if sales:
+ sales_invoice = self.process_merging_into_sales_invoice(sales)
- if len(returns):
+ if returns:
credit_note = self.process_merging_into_credit_note(returns)
- else:
- credit_note = ""
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
index 15f23b6..b9e07b8 100644
--- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
+++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
@@ -5,13 +5,14 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import cint
+from frappe.utils import cint, get_link_to_form
from frappe.model.document import Document
from erpnext.controllers.status_updater import StatusUpdater
class POSOpeningEntry(StatusUpdater):
def validate(self):
self.validate_pos_profile_and_cashier()
+ self.validate_payment_method_account()
self.set_status()
def validate_pos_profile_and_cashier(self):
@@ -20,6 +21,14 @@
if not cint(frappe.db.get_value("User", self.user, "enabled")):
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
+
+ def validate_payment_method_account(self):
+ for d in self.balance_details:
+ account = frappe.db.get_value("Mode of Payment Account",
+ {"parent": d.mode_of_payment, "company": self.company}, "default_account")
+ if not account:
+ frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
+ .format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
def on_submit(self):
self.set_status(update=True)
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py
index 789b4c3..1386b70 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import msgprint, _
-from frappe.utils import cint, now
+from frappe.utils import cint, now, get_link_to_form
from six import iteritems
from frappe.model.document import Document
@@ -13,7 +13,7 @@
self.validate_default_profile()
self.validate_all_link_fields()
self.validate_duplicate_groups()
- self.check_default_payment()
+ self.validate_payment_methods()
def validate_default_profile(self):
for row in self.applicable_for_users:
@@ -52,14 +52,23 @@
if len(customer_groups) != len(set(customer_groups)):
frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
- def check_default_payment(self):
- if self.payments:
- default_mode_of_payment = [d.default for d in self.payments if d.default]
- if not default_mode_of_payment:
- frappe.throw(_("Set default mode of payment"))
+ def validate_payment_methods(self):
+ if not self.payments:
+ frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
- if len(default_mode_of_payment) > 1:
- frappe.throw(_("Multiple default mode of payment is not allowed"))
+ default_mode_of_payment = [d.default for d in self.payments if d.default]
+ if not default_mode_of_payment:
+ frappe.throw(_("Please select a default mode of payment"))
+
+ if len(default_mode_of_payment) > 1:
+ frappe.throw(_("You can only select one mode of payment as default"))
+
+ for d in self.payments:
+ account = frappe.db.get_value("Mode of Payment Account",
+ {"parent": d.mode_of_payment, "company": self.company}, "default_account")
+ if not account:
+ frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
+ .format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
def on_update(self):
self.set_defaults()
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js
index 504941d..05cb7f0 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.js
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js
@@ -7,10 +7,10 @@
},
get_invoice_fields: function(frm) {
- frappe.model.with_doctype("Sales Invoice", () => {
- var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
+ frappe.model.with_doctype("POS Invoice", () => {
+ var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
- d.fieldtype === 'Table') {
+ ['Table', 'Button'].includes(d.fieldtype)) {
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
} else {
return null;
@@ -25,7 +25,7 @@
frappe.ui.form.on("POS Field", {
fieldname: function(frm, doctype, name) {
var doc = frappe.get_doc(doctype, name);
- var df = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
+ var df = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
return doc.fieldname == d.fieldname ? d : null;
})[0];
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 4f6be59..fe5301d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -25,6 +25,12 @@
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.doc.__islocal) {
+ this.frm.trigger('supplier');
+ }
},
refresh: function(doc) {
@@ -135,6 +141,8 @@
}
});
}
+
+ this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
},
unblock_invoice: function() {
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 7b1062f..079f599 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -132,6 +132,11 @@
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 @@
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()
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
index 0e748f8..f9fdc4b 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
@@ -210,7 +210,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-03-12 14:53:47.679439",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges",
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
index a18fec6..b46d2e3 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
@@ -78,7 +78,7 @@
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges Template",
- "owner": "wasim@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"email": 1,
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 71f2e12..92e49d5 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe, erpnext
import frappe.defaults
-from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
+from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form
from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date
from frappe.model.mapper import get_mapped_doc
@@ -1372,7 +1372,7 @@
{"parent": mode_of_payment, "company": company}, "default_account")
if not account:
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
- .format(mode_of_payment))
+ .format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
return {
"account": account
}
@@ -1612,18 +1612,16 @@
payment.type = payment_mode.type
doc.set('payments', [])
- if not pos_profile or not pos_profile.get('payments'):
- for payment_mode in get_all_mode_of_payments(doc):
- append_payment(payment_mode)
- return
-
for pos_payment_method in pos_profile.get('payments'):
pos_payment_method = pos_payment_method.as_dict()
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
- if payment_mode:
- payment_mode[0].default = pos_payment_method.default
- append_payment(payment_mode[0])
+ if not payment_mode:
+ frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
+ .format(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment)), title=_("Missing Account"))
+
+ payment_mode[0].default = pos_payment_method.default
+ append_payment(payment_mode[0])
def get_all_mode_of_payments(doc):
return frappe.db.sql("""
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 01d3903..c12e006d 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -320,7 +320,6 @@
entry['remarks'] = "On cancellation of " + entry['voucher_no']
entry['is_cancelled'] = 1
- entry['posting_date'] = today()
if entry['debit'] or entry['credit']:
make_entry(entry, adv_adj, "Yes")
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
index efc76f9..9703527 100644
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
+++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
@@ -72,7 +72,7 @@
check_plaid_status() {
const me = this;
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
- if (r && r.enabled == "1") {
+ if (r && r.enabled === "1") {
me.plaid_status = "active"
} else {
me.plaid_status = "inactive"
@@ -139,7 +139,7 @@
}
make() {
- const me = this;
+ const me = this;
new frappe.ui.FileUploader({
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
allow_multiple: 0,
@@ -214,31 +214,35 @@
init_config() {
const me = this;
- frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
- .then(result => {
- me.plaid_env = result.plaid_env;
- me.plaid_public_key = result.plaid_public_key;
- me.client_name = result.client_name;
- me.sync_transactions()
- })
+ frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration')
+ .then(result => {
+ me.plaid_env = result.plaid_env;
+ me.client_name = result.client_name;
+ me.link_token = result.link_token;
+ me.sync_transactions();
+ })
}
sync_transactions() {
const me = this;
- frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => {
+ frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (r) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
- bank: v['bank'],
+ bank: r.bank,
bank_account: me.parent.bank_account,
freeze: true
})
.then((result) => {
- let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized")
+ let result_title = (result && result.length > 0)
+ ? __("{0} bank transaction(s) created", [result.length])
+ : __("This bank account is already synchronized");
+
let result_msg = `
- <div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
- <h5 class="text-muted">${result_title}</h5>
- </div>`
+ <div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
+ <h5 class="text-muted">${result_title}</h5>
+ </div>`
+
this.parent.$main_section.append(result_msg)
- frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'});
+ frappe.show_alert({ message: __("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator: 'green' });
})
})
}
@@ -384,7 +388,7 @@
})
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
- {bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")}
+ { bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") }
).then((result) => {
me.make_dialog(result)
})
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index c999eb9..29c4f7d 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -34,7 +34,7 @@
filters: {
'company': company
}
- }
+ };
}
},
{
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 59117c8..044fc1d 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -617,9 +617,19 @@
elif party_type_field=="supplier":
self.add_supplier_filters(conditions, values)
+ if self.filters.cost_center:
+ self.get_cost_center_conditions(conditions)
+
self.add_accounting_dimensions_filters(conditions, values)
return " and ".join(conditions), values
+ def get_cost_center_conditions(self, conditions):
+ lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
+ cost_center_list = [center.name for center in frappe.get_list("Cost Center", filters = {'lft': (">=", lft), 'rgt': ("<=", rgt)})]
+
+ cost_center_string = '", "'.join(cost_center_list)
+ conditions.append('cost_center in ("{0}")'.format(cost_center_string))
+
def get_order_by_condition(self):
if self.filters.get('group_by_party'):
return "order by party, posting_date"
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index 219871b..d011689 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -256,7 +256,7 @@
"""accumulate children's values in parent accounts"""
for d in reversed(accounts):
if d.parent_account:
- account = d.parent_account.split('-')[0].strip()
+ account = d.parent_account.split(' - ')[0].strip()
if not accounts_by_name.get(account):
continue
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 2563b66..84c7454 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -268,9 +268,9 @@
def get_last_purchase_rate(self, item_code, row):
condition = ''
if row.project:
- condition += " AND a.project='%s'" % (row.project)
+ condition += " AND a.project=%s" % (frappe.db.escape(row.project))
elif row.cost_center:
- condition += " AND a.cost_center='%s'" % (row.cost_center)
+ condition += " AND a.cost_center=%s" % (frappe.db.escape(row.cost_center))
if self.filters.to_date:
condition += " AND modified='%s'" % (self.filters.to_date)
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index fba20c0..7ad164a 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -252,13 +252,6 @@
})
},
- available_for_use_date: function(frm) {
- $.each(frm.doc.finance_books || [], function(i, d) {
- if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date;
- });
- refresh_field("finance_books");
- },
-
is_existing_asset: function(frm) {
frm.trigger("toggle_reference_doc");
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
@@ -438,6 +431,15 @@
}
frappe.flags.dont_change_rate = false;
+ },
+
+ depreciation_start_date: function(frm, cdt, cdn) {
+ const book = locals[cdt][cdn];
+ if (frm.doc.available_for_use_date && book.depreciation_start_date == frm.doc.available_for_use_date) {
+ frappe.msgprint(__(`Depreciation Posting Date should not be equal to Available for Use Date.`));
+ book.depreciation_start_date = "";
+ frm.refresh_field("finance_books");
+ }
}
});
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 0bd03a8..efdbdb1 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -6,7 +6,7 @@
import frappe, erpnext, math, json
from frappe import _
from six import string_types
-from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days
+from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days, get_last_day, get_datetime
from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \
@@ -83,6 +83,11 @@
if not self.available_for_use_date:
frappe.throw(_("Available for use date is required"))
+ for d in self.finance_books:
+ if d.depreciation_start_date == self.available_for_use_date:
+ frappe.throw(_("Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.").format(d.idx),
+ title=_("Incorrect Date"))
+
def set_missing_values(self):
if not self.asset_category:
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
@@ -135,6 +140,10 @@
def make_asset_movement(self):
reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
reference_docname = self.purchase_receipt or self.purchase_invoice
+ transaction_date = getdate(self.purchase_date)
+ if reference_docname:
+ posting_date, posting_time = frappe.db.get_value(reference_doctype, reference_docname, ["posting_date", "posting_time"])
+ transaction_date = get_datetime("{} {}".format(posting_date, posting_time))
assets = [{
'asset': self.name,
'asset_name': self.asset_name,
@@ -146,7 +155,7 @@
'assets': assets,
'purpose': 'Receipt',
'company': self.company,
- 'transaction_date': getdate(nowdate()),
+ 'transaction_date': transaction_date,
'reference_doctype': reference_doctype,
'reference_name': reference_docname
}).insert()
@@ -294,7 +303,7 @@
if not row.depreciation_start_date:
if not self.available_for_use_date:
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
- row.depreciation_start_date = self.available_for_use_date
+ row.depreciation_start_date = get_last_day(self.available_for_use_date)
if not self.is_existing_asset:
self.opening_accumulated_depreciation = 0
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index aed78e7..52039c1 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -371,19 +371,18 @@
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
- asset.available_for_use_date = nowdate()
- asset.purchase_date = nowdate()
+ asset.available_for_use_date = '2020-01-01'
+ asset.purchase_date = '2020-01-01'
asset.append("finance_books", {
"expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10,
- "depreciation_start_date": nowdate()
+ "total_number_of_depreciations": 10,
+ "frequency_of_depreciation": 1
})
asset.insert()
asset.submit()
- post_depreciation_entries(date=add_months(nowdate(), 10))
+ post_depreciation_entries(date=add_months('2020-01-01', 4))
scrap_asset(asset.name)
@@ -392,9 +391,9 @@
self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = (
- ("_Test Accumulated Depreciations - _TC", 30000.0, 0.0),
+ ("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
- ("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0)
+ ("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0)
)
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
@@ -466,8 +465,7 @@
"expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10,
- "depreciation_start_date": "2020-06-06"
+ "frequency_of_depreciation": 10
})
asset.insert()
accumulated_depreciation_after_full_schedule = \
diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
index c80f95e..79fcb95 100644
--- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
+++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
@@ -1,347 +1,99 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-05-08 14:44:37.095570",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2018-05-08 14:44:37.095570",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "finance_book",
+ "depreciation_method",
+ "total_number_of_depreciations",
+ "column_break_5",
+ "frequency_of_depreciation",
+ "depreciation_start_date",
+ "expected_value_after_useful_life",
+ "value_after_depreciation",
+ "rate_of_depreciation"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fetch_if_empty": 0,
- "fieldname": "finance_book",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Finance Book",
- "length": 0,
- "no_copy": 0,
- "options": "Finance Book",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "finance_book",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Finance Book",
+ "options": "Finance Book"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "depreciation_method",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Depreciation Method",
- "length": 0,
- "no_copy": 0,
- "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "depreciation_method",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Depreciation Method",
+ "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "total_number_of_depreciations",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Total Number of Depreciations",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "total_number_of_depreciations",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Total Number of Depreciations",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_5",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "frequency_of_depreciation",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Frequency of Depreciation (Months)",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "frequency_of_depreciation",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Frequency of Depreciation (Months)",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:parent.doctype == 'Asset'",
- "fetch_if_empty": 0,
- "fieldname": "depreciation_start_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Depreciation Start Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:parent.doctype == 'Asset'",
+ "fieldname": "depreciation_start_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Depreciation Posting Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "depends_on": "eval:parent.doctype == 'Asset'",
- "fetch_if_empty": 0,
- "fieldname": "expected_value_after_useful_life",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Expected Value After Useful Life",
- "length": 0,
- "no_copy": 0,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "depends_on": "eval:parent.doctype == 'Asset'",
+ "fieldname": "expected_value_after_useful_life",
+ "fieldtype": "Currency",
+ "label": "Expected Value After Useful Life",
+ "options": "Company:company:default_currency"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "value_after_depreciation",
- "fieldtype": "Currency",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Value After Depreciation",
- "length": 0,
- "no_copy": 1,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "value_after_depreciation",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "label": "Value After Depreciation",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
- "description": "In Percentage",
- "fetch_if_empty": 0,
- "fieldname": "rate_of_depreciation",
- "fieldtype": "Percent",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Rate of Depreciation",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
+ "description": "In Percentage",
+ "fieldname": "rate_of_depreciation",
+ "fieldtype": "Percent",
+ "label": "Rate of Depreciation"
}
- ],
- "has_web_view": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2019-04-09 19:45:14.523488",
- "modified_by": "Administrator",
- "module": "Assets",
- "name": "Asset Finance Book",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-09-16 12:11:30.631788",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Finance Book",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py
index c3755a3..cddee5f 100644
--- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py
@@ -32,8 +32,7 @@
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10,
- "depreciation_start_date": "2020-06-06"
+ "frequency_of_depreciation": 10
})
if asset.docstatus == 0:
@@ -82,8 +81,7 @@
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
- "frequency_of_depreciation": 10,
- "depreciation_start_date": "2020-06-06"
+ "frequency_of_depreciation": 10
})
if asset.docstatus == 0:
asset.submit()
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 4201e0b..d1063b1 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -1084,7 +1084,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2020-07-31 14:13:44.610190",
+ "modified": "2020-09-14 14:36:12.418690",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@@ -1135,5 +1135,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "supplier",
- "title_field": "supplier"
+ "title_field": "supplier",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 813286f..158799c 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -89,7 +89,7 @@
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
- def test_update_child_qty_rate(self):
+ def test_update_child(self):
mr = make_material_request(qty=10)
po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier"
@@ -119,7 +119,7 @@
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
- def test_add_new_item_in_update_child_qty_rate(self):
+ def test_update_child_adding_new_item(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
po.save()
@@ -145,7 +145,7 @@
self.assertEqual(po.status, 'To Receive and Bill')
- def test_remove_item_in_update_child_qty_rate(self):
+ def test_update_child_removing_item(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
po.save()
@@ -185,7 +185,7 @@
self.assertEquals(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill')
- def test_update_child_qty_rate_perm(self):
+ def test_update_child_perm(self):
po = create_purchase_order(item_code= "_Test Item", qty=4)
user = 'test@example.com'
@@ -202,6 +202,72 @@
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
frappe.set_user("Administrator")
+ def test_update_child_with_tax_template(self):
+ tax_template = "_Test Account Excise Duty @ 10"
+ item = "_Test Item Home Desktop 100"
+
+ if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
+ item_doc = frappe.get_doc("Item", item)
+ item_doc.append("taxes", {
+ "item_tax_template": tax_template,
+ "valid_from": nowdate()
+ })
+ item_doc.save()
+ else:
+ # update valid from
+ frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = CURDATE()
+ where parent = %(item)s and item_tax_template = %(tax)s""",
+ {"item": item, "tax": tax_template})
+
+ po = create_purchase_order(item_code=item, qty=1, do_not_save=1)
+
+ po.append("taxes", {
+ "account_head": "_Test Account Excise Duty - _TC",
+ "charge_type": "On Net Total",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Excise Duty",
+ "doctype": "Purchase Taxes and Charges",
+ "rate": 10
+ })
+ po.insert()
+ po.submit()
+
+ self.assertEqual(po.taxes[0].tax_amount, 50)
+ self.assertEqual(po.taxes[0].total, 550)
+
+ items = json.dumps([
+ {'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name},
+ {'item_code' : item, 'rate' : 100, 'qty' : 1} # added item
+ ])
+ update_child_qty_rate('Purchase Order', items, po.name)
+
+ po.reload()
+ self.assertEqual(po.taxes[0].tax_amount, 60)
+ self.assertEqual(po.taxes[0].total, 660)
+
+ frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
+ where parent = %(item)s and item_tax_template = %(tax)s""",
+ {"item": item, "tax": tax_template})
+
+ def test_update_child_uom_conv_factor_change(self):
+ po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
+ total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
+
+ trans_item = json.dumps([{
+ 'item_code': po.get("items")[0].item_code,
+ 'rate': po.get("items")[0].rate,
+ 'qty': po.get("items")[0].qty,
+ 'uom': "_Test UOM 1",
+ 'conversion_factor': 2,
+ 'docname': po.get("items")[0].name
+ }])
+ update_child_qty_rate('Purchase Order', trans_item, po.name)
+ po.reload()
+
+ total_reqd_qty_after_change = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
+
+ self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty)
+
def test_update_qty(self):
po = create_purchase_order()
diff --git a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
index c3e1bf5..d7ea9c1 100644
--- a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
+++ b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
@@ -148,11 +148,11 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-03-12 15:43:53.862897",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item Supplied",
- "owner": "dhanalekshmi@webnotestech.com",
+ "owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
index ea58630..dc00bca 100644
--- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
+++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
@@ -188,11 +188,11 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-04-10 18:09:33.997618",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Receipt Item Supplied",
- "owner": "wasim@webnotestech.com",
+ "owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
index af109ba..e956afd 100644
--- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
@@ -178,6 +178,7 @@
period_card = make_supplier_scorecard(docname, None)
period_card.start_date = start_date
period_card.end_date = end_date
+ period_card.insert(ignore_permissions=True)
period_card.submit()
scp_count = scp_count + 1
if start_date < first_start_date:
diff --git a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py
index 87f1033..9938710 100644
--- a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py
+++ b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py
@@ -106,7 +106,7 @@
"doctype": "Supplier Scorecard Scoring Criteria",
"postprocess": update_criteria_fields,
}
- }, target_doc, post_process)
+ }, target_doc, post_process, ignore_permissions=True)
return doc
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index d61e44b..90c466b 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -7,6 +7,7 @@
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
@@ -19,7 +20,7 @@
from erpnext.exceptions import InvalidCurrency
from six import text_type
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
-from erpnext.stock.get_item_details import get_item_warehouse
+from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
@@ -1157,6 +1158,18 @@
}
return info
+def set_child_tax_template_and_map(item, child_item, parent_doc):
+ args = {
+ 'item_code': item.item_code,
+ 'posting_date': parent_doc.transaction_date,
+ 'tax_category': parent_doc.get('tax_category'),
+ 'company': parent_doc.get('company')
+ }
+
+ child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
+ if child_item.get("item_tax_template"):
+ child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
+
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
"""
Returns a Sales Order Item child item containing the default values
@@ -1168,8 +1181,10 @@
child_item.item_name = item.item_name
child_item.description = item.description
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
- child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
- child_item.uom = item.stock_uom
+ child_item.uom = trans_item.get("uom") or item.stock_uom
+ conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
+ child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
+ set_child_tax_template_and_map(item, child_item, p_doc)
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse:
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
@@ -1188,13 +1203,15 @@
child_item.item_name = item.item_name
child_item.description = item.description
child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date
- child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
- child_item.uom = item.stock_uom
+ child_item.uom = trans_item.get("uom") or item.stock_uom
+ conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
+ child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
child_item.base_rate = 1 # Initiallize value will update in parent validation
child_item.base_amount = 1 # Initiallize value will update in parent validation
+ 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:
@@ -1221,18 +1238,40 @@
@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 are not allowed to update as per the conditions set in {} Workflow.").format(get_link_to_form("Workflow", workflow)),
+ title=_("Insufficient Permissions")
+ )
def get_new_child_item(item_row):
- 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):
@@ -1246,21 +1285,23 @@
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"))
prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
+ prev_uom, new_uom = child_item.get("uom"), d.get("uom")
if parent_doctype == 'Sales Order':
prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
@@ -1269,9 +1310,10 @@
rate_unchanged = prev_rate == new_rate
qty_unchanged = prev_qty == new_qty
+ uom_unchanged = prev_uom == new_uom
conversion_factor_unchanged = prev_con_fac == new_con_fac
date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
- if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged:
+ if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged:
continue
validate_quantity(child_item, d)
@@ -1291,6 +1333,11 @@
else:
child_item.conversion_factor = flt(d.get('conversion_factor'))
+ if d.get("uom"):
+ child_item.uom = d.get("uom")
+ conversion_factor = flt(get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor"))
+ child_item.conversion_factor = flt(d.get('conversion_factor')) or conversion_factor
+
if d.get("delivery_date") and parent_doctype == 'Sales Order':
child_item.delivery_date = d.get('delivery_date')
@@ -1356,12 +1403,17 @@
parent.update_receiving_percentage()
if parent.is_subcontracted == "Yes":
parent.update_reserved_qty_for_subcontract()
+ parent.create_raw_materials_supplied("supplied_items")
+ parent.save()
else:
parent.update_reserved_qty()
parent.update_project()
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()
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index c88bf66..efd4944 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -165,9 +165,14 @@
AND company = %(company)s
AND account_currency = %(currency)s
AND `{searchfield}` LIKE %(txt)s
+ {mcond}
ORDER BY idx DESC, name
LIMIT %(offset)s, %(limit)s
- """.format(account_type_condition=account_type_condition, searchfield=searchfield),
+ """.format(
+ account_type_condition=account_type_condition,
+ searchfield=searchfield,
+ mcond=get_match_cond(doctype)
+ ),
dict(
account_types=filters.get("account_type"),
company=filters.get("company"),
@@ -359,9 +364,21 @@
if filters.get("is_return"):
having_clause = ""
+ meta = frappe.get_meta("Batch", cached=True)
+ searchfields = meta.get_search_fields()
+
+ search_columns = ''
+ if searchfields:
+ search_columns = ", " + ", ".join(searchfields)
+
if args.get('warehouse'):
+ searchfields = ['batch.' + field for field in searchfields]
+ if searchfields:
+ search_columns = ", " + ", ".join(searchfields)
+
batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
+ {search_columns}
from `tabStock Ledger Entry` sle
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
where
@@ -377,6 +394,7 @@
group by batch_no {having_clause}
order by batch.expiry_date, sle.batch_no desc
limit %(start)s, %(page_len)s""".format(
+ search_columns = search_columns,
cond=cond,
match_conditions=get_match_cond(doctype),
having_clause = having_clause
@@ -384,7 +402,9 @@
return batch_nos
else:
- return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date) from `tabBatch` batch
+ return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date)
+ {search_columns}
+ from `tabBatch` batch
where batch.disabled = 0
and item = %(item_code)s
and (name like %(txt)s
@@ -394,7 +414,7 @@
{0}
{match_conditions}
order by expiry_date, name desc
- limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args)
+ limit %(start)s, %(page_len)s""".format(cond, search_columns = search_columns, match_conditions=get_match_cond(doctype)), args)
@frappe.whitelist()
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index a03dee1..afc5f81 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -242,7 +242,8 @@
'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
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 17f3ae5..7f7aae3 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -81,6 +81,7 @@
party_details = _get_party_details(customer,
ignore_permissions=self.flags.ignore_permissions,
doctype=self.doctype, company=self.company,
+ posting_date=self.get('posting_date'),
fetch_payment_terms_template=fetch_payment_terms_template,
party_address=self.customer_address, shipping_address=self.shipping_address_name)
if not self.meta.get_field("sales_team"):
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 0dc9878..9feac78 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -255,7 +255,7 @@
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"] = ""
diff --git a/erpnext/crm/desk_page/crm/crm.json b/erpnext/crm/desk_page/crm/crm.json
index eb69dc0..d974beb 100644
--- a/erpnext/crm/desk_page/crm/crm.json
+++ b/erpnext/crm/desk_page/crm/crm.json
@@ -8,7 +8,7 @@
{
"hidden": 0,
"label": "Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Prospects Engaged But Not Converted\",\n \"name\": \"Prospects Engaged But Not Converted\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Opportunity\"\n ],\n \"doctype\": \"Opportunity\",\n \"is_query_report\": true,\n \"label\": \"Minutes to First Response for Opportunity\",\n \"name\": \"Minutes to First Response for Opportunity\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Campaign Efficiency\",\n \"name\": \"Campaign Efficiency\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Owner Efficiency\",\n \"name\": \"Lead Owner Efficiency\",\n \"type\": \"report\"\n }\n]"
+ "links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Prospects Engaged But Not Converted\",\n \"name\": \"Prospects Engaged But Not Converted\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Opportunity\"\n ],\n \"doctype\": \"Opportunity\",\n \"is_query_report\": true,\n \"label\": \"First Response Time for Opportunity\",\n \"name\": \"First Response Time for Opportunity\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Campaign Efficiency\",\n \"name\": \"Campaign Efficiency\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Owner Efficiency\",\n \"name\": \"Lead Owner Efficiency\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
@@ -42,7 +42,7 @@
"idx": 0,
"is_standard": 1,
"label": "CRM",
- "modified": "2020-05-28 13:33:52.906750",
+ "modified": "2020-08-11 18:55:18.238900",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 315d298..31db569 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -27,9 +27,6 @@
def after_insert(self):
self.update_links()
- # after the address and contact are created, flush the field values
- # to avoid inconsistent reporting in case the documents are changed
- self.flush_address_and_contact_fields()
def validate(self):
self.set_lead_name()
@@ -210,14 +207,6 @@
})
self.contact_doc.save()
- def flush_address_and_contact_fields(self):
- fields = ['address_type', 'address_line1', 'address_line2', 'address_title',
- 'city', 'county', 'country', 'fax', 'pincode', 'state']
-
- for field in fields:
- self.set(field, None)
-
-
@frappe.whitelist()
def make_customer(source_name, target_doc=None):
return _make_customer(source_name, target_doc)
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index b61cad3..eee13f7 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -24,7 +24,7 @@
"converted_by",
"sales_stage",
"order_lost_reason",
- "mins_to_first_response",
+ "first_response_time",
"expected_closing",
"next_contact",
"contact_by",
@@ -153,13 +153,6 @@
"read_only": 1
},
{
- "bold": 1,
- "fieldname": "mins_to_first_response",
- "fieldtype": "Float",
- "label": "Mins to first response",
- "read_only": 1
- },
- {
"fieldname": "expected_closing",
"fieldtype": "Date",
"label": "Expected Closing Date"
@@ -419,12 +412,19 @@
"fieldtype": "Link",
"label": "Converted By",
"options": "User"
+ },
+ {
+ "bold": 1,
+ "fieldname": "first_response_time",
+ "fieldtype": "Duration",
+ "label": "First Response Time",
+ "read_only": 1
}
],
"icon": "fa fa-info-sign",
"idx": 195,
"links": [],
- "modified": "2020-08-11 17:34:35.066961",
+ "modified": "2020-08-12 17:34:35.066961",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",
diff --git a/erpnext/crm/report/minutes_to_first_response_for_opportunity/__init__.py b/erpnext/crm/report/first_response_time_for_opportunity/__init__.py
similarity index 100%
rename from erpnext/crm/report/minutes_to_first_response_for_opportunity/__init__.py
rename to erpnext/crm/report/first_response_time_for_opportunity/__init__.py
diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
new file mode 100644
index 0000000..3f5c95a
--- /dev/null
+++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
@@ -0,0 +1,44 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["First Response Time for Opportunity"] = {
+ "filters": [
+ {
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.add_days(frappe.datetime.nowdate(), -30)
+ },
+ {
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.nowdate()
+ },
+ ],
+ get_chart_data: function (_columns, result) {
+ return {
+ data: {
+ labels: result.map(d => d[0]),
+ datasets: [{
+ name: "First Response Time",
+ values: result.map(d => d[1])
+ }]
+ },
+ type: "line",
+ tooltipOptions: {
+ formatTooltipY: d => {
+ let duration_options = {
+ hide_days: 0,
+ hide_seconds: 0
+ };
+ value = frappe.utils.get_formatted_duration(d, duration_options);
+ return value;
+ }
+ }
+ }
+ }
+};
diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.json b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.json
new file mode 100644
index 0000000..1b3184f
--- /dev/null
+++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.json
@@ -0,0 +1,28 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-08-10 18:34:19.083872",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Test 2",
+ "modified": "2020-08-10 18:34:19.083872",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "First Response Time for Opportunity",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Opportunity",
+ "report_name": "First Response Time for Opportunity",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Sales User"
+ },
+ {
+ "role": "Sales Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py
new file mode 100644
index 0000000..2ffbc3e
--- /dev/null
+++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute(filters=None):
+ columns = [
+ {
+ 'fieldname': 'creation_date',
+ 'label': 'Date',
+ 'fieldtype': 'Date',
+ 'width': 300
+ },
+ {
+ 'fieldname': 'first_response_time',
+ 'fieldtype': 'Duration',
+ 'label': 'First Response Time',
+ 'width': 300
+ },
+ ]
+
+ data = frappe.db.sql('''
+ SELECT
+ date(creation) as creation_date,
+ avg(first_response_time) as avg_response_time
+ FROM tabOpportunity
+ WHERE
+ date(creation) between %s and %s
+ and first_response_time > 0
+ GROUP BY creation_date
+ ORDER BY creation_date desc
+ ''', (filters.from_date, filters.to_date))
+
+ return columns, data
diff --git a/erpnext/crm/report/minutes_to_first_response_for_opportunity/minutes_to_first_response_for_opportunity.js b/erpnext/crm/report/minutes_to_first_response_for_opportunity/minutes_to_first_response_for_opportunity.js
deleted file mode 100644
index 92d026a..0000000
--- a/erpnext/crm/report/minutes_to_first_response_for_opportunity/minutes_to_first_response_for_opportunity.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.query_reports["Minutes to First Response for Opportunity"] = {
- "filters": [
- {
- "fieldname": "from_date",
- "label": __("From Date"),
- "fieldtype": "Date",
- 'reqd': 1,
- "default": frappe.datetime.add_days(frappe.datetime.nowdate(), -30)
- },
- {
- "fieldname": "to_date",
- "label": __("To Date"),
- "fieldtype": "Date",
- 'reqd': 1,
- "default": frappe.datetime.nowdate()
- },
- ],
- get_chart_data: function (columns, result) {
- return {
- data: {
- labels: result.map(d => d[0]),
- datasets: [{
- name: 'Mins to first response',
- values: result.map(d => d[1])
- }]
- },
- type: 'line',
- }
- }
-}
diff --git a/erpnext/crm/report/minutes_to_first_response_for_opportunity/minutes_to_first_response_for_opportunity.json b/erpnext/crm/report/minutes_to_first_response_for_opportunity/minutes_to_first_response_for_opportunity.json
deleted file mode 100644
index bcd092b..0000000
--- a/erpnext/crm/report/minutes_to_first_response_for_opportunity/minutes_to_first_response_for_opportunity.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "add_total_row": 0,
- "apply_user_permissions": 0,
- "creation": "2016-06-17 11:28:25.867258",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 2,
- "is_standard": "Yes",
- "modified": "2017-02-24 20:06:08.801109",
- "modified_by": "Administrator",
- "module": "CRM",
- "name": "Minutes to First Response for Opportunity",
- "owner": "Administrator",
- "ref_doctype": "Opportunity",
- "report_name": "Minutes to First Response for Opportunity",
- "report_type": "Script Report",
- "roles": [
- {
- "role": "Sales User"
- },
- {
- "role": "Sales Manager"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/crm/report/minutes_to_first_response_for_opportunity/minutes_to_first_response_for_opportunity.py b/erpnext/crm/report/minutes_to_first_response_for_opportunity/minutes_to_first_response_for_opportunity.py
deleted file mode 100644
index 54e3a60..0000000
--- a/erpnext/crm/report/minutes_to_first_response_for_opportunity/minutes_to_first_response_for_opportunity.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-
-def execute(filters=None):
- columns = [
- {
- 'fieldname': 'creation_date',
- 'label': 'Date',
- 'fieldtype': 'Date'
- },
- {
- 'fieldname': 'mins',
- 'fieldtype': 'Float',
- 'label': 'Mins to First Response'
- },
- ]
-
- data = frappe.db.sql('''select date(creation) as creation_date,
- avg(mins_to_first_response) as mins
- from tabOpportunity
- where date(creation) between %s and %s
- and mins_to_first_response > 0
- group by creation_date order by creation_date desc''', (filters.from_date, filters.to_date))
-
- return columns, data
diff --git a/erpnext/education/doctype/question/question.json b/erpnext/education/doctype/question/question.json
index b3a161d..e396760 100644
--- a/erpnext/education/doctype/question/question.json
+++ b/erpnext/education/doctype/question/question.json
@@ -13,7 +13,7 @@
"fields": [
{
"fieldname": "question",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Question",
"reqd": 1
@@ -34,7 +34,7 @@
"read_only": 1
}
],
- "modified": "2019-05-30 18:39:21.880974",
+ "modified": "2020-09-24 18:39:21.880974",
"modified_by": "Administrator",
"module": "Education",
"name": "Question",
@@ -77,4 +77,4 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/education/doctype/quiz_question/quiz_question.json b/erpnext/education/doctype/quiz_question/quiz_question.json
index 0564482..aab07a3 100644
--- a/erpnext/education/doctype/quiz_question/quiz_question.json
+++ b/erpnext/education/doctype/quiz_question/quiz_question.json
@@ -20,14 +20,14 @@
{
"fetch_from": "question_link.question",
"fieldname": "question",
- "fieldtype": "Data",
+ "fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Question",
"read_only": 1
}
],
"istable": 1,
- "modified": "2019-06-12 12:24:02.312577",
+ "modified": "2020-09-24 12:24:02.312577",
"modified_by": "Administrator",
"module": "Education",
"name": "Quiz Question",
@@ -37,4 +37,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/education/doctype/student/student.js b/erpnext/education/doctype/student/student.js
index 1936dcb..fd23ae4 100644
--- a/erpnext/education/doctype/student/student.js
+++ b/erpnext/education/doctype/student/student.js
@@ -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 @@
{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);
+ }
+ });
}
});
diff --git a/erpnext/education/doctype/student/student.json b/erpnext/education/doctype/student/student.json
index ac65c0c..8ba9a7f 100644
--- a/erpnext/education/doctype/student/student.json
+++ b/erpnext/education/doctype/student/student.json
@@ -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",
diff --git a/erpnext/education/doctype/student_applicant/student_applicant.js b/erpnext/education/doctype/student_applicant/student_applicant.js
index 83621c5..b4cfdf1 100644
--- a/erpnext/education/doctype/student_applicant/student_applicant.js
+++ b/erpnext/education/doctype/student_applicant/student_applicant.js
@@ -7,7 +7,7 @@
},
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 @@
}, '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.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) {
diff --git a/erpnext/education/doctype/student_applicant/student_applicant.json b/erpnext/education/doctype/student_applicant/student_applicant.json
index e5d0bd3..bca38fb 100644
--- a/erpnext/education/doctype/student_applicant/student_applicant.json
+++ b/erpnext/education/doctype/student_applicant/student_applicant.json
@@ -1,1233 +1,292 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2015-09-11 11:50:09.740807",
- "custom": 0,
- "default_print_format": "",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_import": 1,
+ "autoname": "naming_series:",
+ "creation": "2015-09-11 11:50:09.740807",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "first_name",
+ "middle_name",
+ "last_name",
+ "program",
+ "lms_only",
+ "paid",
+ "column_break_8",
+ "naming_series",
+ "application_status",
+ "application_date",
+ "academic_year",
+ "academic_term",
+ "student_admission",
+ "image",
+ "section_break_4",
+ "date_of_birth",
+ "gender",
+ "blood_group",
+ "column_break_12",
+ "student_email_id",
+ "student_mobile_number",
+ "nationality",
+ "home_address",
+ "address_line_1",
+ "address_line_2",
+ "pincode",
+ "column_break_23",
+ "city",
+ "state",
+ "section_break_20",
+ "guardians",
+ "section_break_21",
+ "siblings",
+ "section_break_23",
+ "title",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "first_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "First Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "first_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "label": "First Name",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "middle_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Middle Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "middle_name",
+ "fieldtype": "Data",
+ "label": "Middle Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "last_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Last Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "last_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "label": "Last Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "program",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Program",
- "length": 0,
- "no_copy": 0,
- "options": "Program",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "program",
+ "fieldtype": "Link",
+ "in_filter": 1,
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Program",
+ "options": "Program",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fieldname": "lms_only",
- "fieldtype": "Check",
- "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": "LMS Only",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "lms_only",
+ "fieldtype": "Check",
+ "label": "LMS Only"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "paid",
- "fieldtype": "Check",
- "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": "Paid",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "paid",
+ "fieldtype": "Check",
+ "label": "Paid"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_8",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Naming Series",
- "length": 0,
- "no_copy": 1,
- "options": "EDU-APP-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "no_copy": 1,
+ "options": "EDU-APP-.YYYY.-",
+ "set_only_once": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.docstatus != 0",
- "fieldname": "application_status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Application Status",
- "length": 0,
- "no_copy": 1,
- "options": "Applied\nApproved\nRejected\nAdmitted",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "depends_on": "eval:doc.docstatus != 0",
+ "fieldname": "application_status",
+ "fieldtype": "Select",
+ "in_filter": 1,
+ "label": "Application Status",
+ "no_copy": 1,
+ "options": "Applied\nApproved\nRejected\nAdmitted"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fieldname": "application_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Application Date",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Today",
+ "fieldname": "application_date",
+ "fieldtype": "Date",
+ "label": "Application Date"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "academic_year",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Academic Year",
- "length": 0,
- "no_copy": 0,
- "options": "Academic Year",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "academic_year",
+ "fieldtype": "Link",
+ "label": "Academic Year",
+ "options": "Academic Year"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "academic_term",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Academic Term",
- "length": 0,
- "no_copy": 0,
- "options": "Academic Term",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "academic_term",
+ "fieldtype": "Link",
+ "label": "Academic Term",
+ "options": "Academic Term"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student_admission",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Student Admission",
- "length": 0,
- "no_copy": 0,
- "options": "Student Admission",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "student_admission",
+ "fieldtype": "Link",
+ "label": "Student Admission",
+ "options": "Student Admission"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "image",
- "fieldtype": "Attach Image",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Image",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "hidden": 1,
+ "label": "Image"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "collapsible_depends_on": "",
- "columns": 0,
- "fieldname": "section_break_4",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Personal Details",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break",
+ "label": "Personal Details"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "date_of_birth",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Date of Birth",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "date_of_birth",
+ "fieldtype": "Date",
+ "label": "Date of Birth"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "gender",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Gender",
- "length": 0,
- "no_copy": 0,
- "options": "\nMale\nFemale",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "gender",
+ "fieldtype": "Select",
+ "label": "Gender",
+ "options": "\nMale\nFemale"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "blood_group",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Blood Group",
- "length": 0,
- "no_copy": 0,
- "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "blood_group",
+ "fieldtype": "Select",
+ "label": "Blood Group",
+ "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_12",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student_email_id",
- "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": "Student Email Address",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "fieldname": "student_email_id",
+ "fieldtype": "Data",
+ "label": "Student Email Address",
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student_mobile_number",
- "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": "Student Mobile Number",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "student_mobile_number",
+ "fieldtype": "Data",
+ "label": "Student Mobile Number"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "nationality",
- "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": "Nationality",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "nationality",
+ "fieldtype": "Data",
+ "label": "Nationality"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "home_address",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Home Address",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "home_address",
+ "fieldtype": "Section Break",
+ "label": "Home Address"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_line_1",
- "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": "Address Line 1",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "address_line_1",
+ "fieldtype": "Data",
+ "label": "Address Line 1"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_line_2",
- "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": "Address Line 2",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "address_line_2",
+ "fieldtype": "Data",
+ "label": "Address Line 2"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "pincode",
- "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": "Pincode",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "pincode",
+ "fieldtype": "Data",
+ "label": "Pincode"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_23",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_23",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "city",
- "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": "City",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "city",
+ "fieldtype": "Data",
+ "label": "City"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "state",
- "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": "State",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "state",
+ "fieldtype": "Data",
+ "label": "State"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "columns": 0,
- "fieldname": "section_break_20",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Guardian Details",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "collapsible": 1,
+ "fieldname": "section_break_20",
+ "fieldtype": "Section Break",
+ "label": "Guardian Details"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "guardians",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Guardians",
- "length": 0,
- "no_copy": 0,
- "options": "Student Guardian",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "guardians",
+ "fieldtype": "Table",
+ "label": "Guardians",
+ "options": "Student Guardian"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "columns": 0,
- "fieldname": "section_break_21",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sibling Details",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "collapsible": 1,
+ "fieldname": "section_break_21",
+ "fieldtype": "Section Break",
+ "label": "Sibling Details"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "siblings",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Siblings",
- "length": 0,
- "no_copy": 0,
- "options": "Student Sibling",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "siblings",
+ "fieldtype": "Table",
+ "label": "Siblings",
+ "options": "Student Sibling"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_23",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_23",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "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": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Title"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Student Applicant",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Student Applicant",
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_field": "image",
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "menu_index": 0,
- "modified": "2018-10-05 13:15:59.283862",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Applicant",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "image_field": "image",
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-09-07 19:31:30.063563",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Applicant",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 1,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Academics User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "import": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Academics User",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "title",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
-}
+ ],
+ "restrict_to_domain": "Education",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "title"
+}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
index 532e19c..a033a2a 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
@@ -2,81 +2,90 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
-from __future__ import unicode_literals
-from frappe import _
-from frappe.utils.password import get_decrypted_password
-from plaid import Client
-from plaid.errors import APIError, ItemError
+import plaid
+import requests
+from plaid.errors import APIError, ItemError, InvalidRequestError
import frappe
-import requests
+from frappe import _
+
class PlaidConnector():
def __init__(self, access_token=None):
-
- plaid_settings = frappe.get_single("Plaid Settings")
-
- self.config = {
- "plaid_client_id": plaid_settings.plaid_client_id,
- "plaid_secret": get_decrypted_password("Plaid Settings", "Plaid Settings", 'plaid_secret'),
- "plaid_public_key": plaid_settings.plaid_public_key,
- "plaid_env": plaid_settings.plaid_env
- }
-
- self.client = Client(client_id=self.config.get("plaid_client_id"),
- secret=self.config.get("plaid_secret"),
- public_key=self.config.get("plaid_public_key"),
- environment=self.config.get("plaid_env")
- )
-
self.access_token = access_token
+ self.settings = frappe.get_single("Plaid Settings")
+ self.products = ["auth", "transactions"]
+ self.client_name = frappe.local.site
+ self.client = plaid.Client(
+ client_id=self.settings.plaid_client_id,
+ secret=self.settings.get_password("plaid_secret"),
+ environment=self.settings.plaid_env,
+ api_version="2019-05-29"
+ )
def get_access_token(self, public_token):
if public_token is None:
frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error"))
-
response = self.client.Item.public_token.exchange(public_token)
- access_token = response['access_token']
-
+ access_token = response["access_token"]
return access_token
+ def get_link_token(self):
+ token_request = {
+ "client_name": self.client_name,
+ "client_id": self.settings.plaid_client_id,
+ "secret": self.settings.plaid_secret,
+ "products": self.products,
+ # only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
+ "language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
+ "country_codes": ["US", "CA", "FR", "IE", "NL", "ES", "GB"],
+ "user": {
+ "client_user_id": frappe.generate_hash(frappe.session.user, length=32)
+ }
+ }
+
+ try:
+ response = self.client.LinkToken.create(token_request)
+ except InvalidRequestError:
+ frappe.log_error(frappe.get_traceback(), _("Plaid invalid request error"))
+ frappe.msgprint(_("Please check your Plaid client ID and secret values"))
+ except APIError as e:
+ frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
+ frappe.throw(_(str(e)), title=_("Authentication Failed"))
+ else:
+ return response["link_token"]
+
def auth(self):
try:
self.client.Auth.get(self.access_token)
- print("Authentication successful.....")
except ItemError as e:
- if e.code == 'ITEM_LOGIN_REQUIRED':
- pass
- else:
+ if e.code == "ITEM_LOGIN_REQUIRED":
pass
except APIError as e:
- if e.code == 'PLANNED_MAINTENANCE':
- pass
- else:
+ if e.code == "PLANNED_MAINTENANCE":
pass
except requests.Timeout:
pass
except Exception as e:
- print(e)
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
- frappe.msgprint({"title": _("Authentication Failed"), "message":e, "raise_exception":1, "indicator":'red'})
+ frappe.throw(_(str(e)), title=_("Authentication Failed"))
def get_transactions(self, start_date, end_date, account_id=None):
+ self.auth()
+ kwargs = dict(
+ access_token=self.access_token,
+ start_date=start_date,
+ end_date=end_date
+ )
+ if account_id:
+ kwargs.update(dict(account_ids=[account_id]))
+
try:
- self.auth()
- if account_id:
- account_ids = [account_id]
-
- response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids)
-
- else:
- response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date)
-
- transactions = response['transactions']
-
- while len(transactions) < response['total_transactions']:
+ response = self.client.Transactions.get(**kwargs)
+ transactions = response["transactions"]
+ while len(transactions) < response["total_transactions"]:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
- transactions.extend(response['transactions'])
+ transactions.extend(response["transactions"])
return transactions
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
index 0ffbb87..22a4004 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
@@ -4,14 +4,14 @@
frappe.provide("erpnext.integrations");
frappe.ui.form.on('Plaid Settings', {
- enabled: function(frm) {
+ enabled: function (frm) {
frm.toggle_reqd('plaid_client_id', frm.doc.enabled);
frm.toggle_reqd('plaid_secret', frm.doc.enabled);
- frm.toggle_reqd('plaid_public_key', frm.doc.enabled);
frm.toggle_reqd('plaid_env', frm.doc.enabled);
},
- refresh: function(frm) {
- if(frm.doc.enabled) {
+
+ refresh: function (frm) {
+ if (frm.doc.enabled) {
frm.add_custom_button('Link a new bank account', () => {
new erpnext.integrations.plaidLink(frm);
});
@@ -22,17 +22,16 @@
erpnext.integrations.plaidLink = class plaidLink {
constructor(parent) {
this.frm = parent;
- this.product = ["transactions", "auth"];
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
this.init_config();
}
- init_config() {
- const me = this;
- me.plaid_env = me.frm.doc.plaid_env;
- me.plaid_public_key = me.frm.doc.plaid_public_key;
- me.client_name = frappe.boot.sitename;
- me.init_plaid();
+ async init_config() {
+ this.product = ["auth", "transactions"];
+ this.plaid_env = this.frm.doc.plaid_env;
+ this.client_name = frappe.boot.sitename;
+ this.token = await this.frm.call("get_link_token").then(resp => resp.message);
+ this.init_plaid();
}
init_plaid() {
@@ -69,17 +68,17 @@
}
onScriptLoaded(me) {
- me.linkHandler = window.Plaid.create({
+ me.linkHandler = Plaid.create({
clientName: me.client_name,
+ product: me.product,
env: me.plaid_env,
- key: me.plaid_public_key,
- onSuccess: me.plaid_success,
- product: me.product
+ token: me.token,
+ onSuccess: me.plaid_success
});
}
onScriptError(error) {
- frappe.msgprint('There was an issue loading the link-initialize.js script');
+ frappe.msgprint("There was an issue connecting to Plaid's authentication server");
frappe.msgprint(error);
}
@@ -87,21 +86,25 @@
const me = this;
frappe.prompt({
- fieldtype:"Link",
+ fieldtype: "Link",
options: "Company",
- label:__("Company"),
- fieldname:"company",
- reqd:1
+ label: __("Company"),
+ fieldname: "company",
+ reqd: 1
}, (data) => {
me.company = data.company;
- frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response})
- .then((result) => {
- frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response,
- bank: result, company: me.company});
- })
- .then(() => {
- frappe.show_alert({message:__("Bank accounts added"), indicator:'green'});
+ frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {
+ token: token,
+ response: response
+ }).then((result) => {
+ frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {
+ response: response,
+ bank: result,
+ company: me.company
});
+ }).then(() => {
+ frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' });
+ });
}, __("Select a company"), __("Continue"));
}
};
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json
index d8203d7..2706217 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json
@@ -1,5 +1,4 @@
{
- "actions": [],
"creation": "2018-10-25 10:02:48.656165",
"doctype": "DocType",
"editable_grid": 1,
@@ -12,7 +11,6 @@
"plaid_client_id",
"plaid_secret",
"column_break_7",
- "plaid_public_key",
"plaid_env"
],
"fields": [
@@ -42,12 +40,6 @@
"label": "Plaid Secret"
},
{
- "fieldname": "plaid_public_key",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Plaid Public Key"
- },
- {
"fieldname": "plaid_env",
"fieldtype": "Select",
"in_list_view": 1,
@@ -69,8 +61,7 @@
}
],
"issingle": 1,
- "links": [],
- "modified": "2020-02-07 15:21:11.616231",
+ "modified": "2020-09-12 02:31:44.542385",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Plaid Settings",
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index c3371ed..e535e81 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -2,30 +2,36 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
-from __future__ import unicode_literals
-import frappe
import json
-from frappe import _
-from frappe.model.document import Document
+
+import frappe
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector
-from frappe.utils import getdate, formatdate, today, add_months
+from frappe import _
from frappe.desk.doctype.tag.tag import add_tag
+from frappe.model.document import Document
+from frappe.utils import add_months, formatdate, getdate, today
+
class PlaidSettings(Document):
- pass
+ @staticmethod
+ def get_link_token():
+ plaid = PlaidConnector()
+ return plaid.get_link_token()
+
@frappe.whitelist()
-def plaid_configuration():
+def get_plaid_configuration():
if frappe.db.get_single_value("Plaid Settings", "enabled"):
plaid_settings = frappe.get_single("Plaid Settings")
return {
- "plaid_public_key": plaid_settings.plaid_public_key,
"plaid_env": plaid_settings.plaid_env,
+ "link_token": plaid_settings.get_link_token(),
"client_name": frappe.local.site
}
- else:
- return "disabled"
+
+ return "disabled"
+
@frappe.whitelist()
def add_institution(token, response):
@@ -33,6 +39,7 @@
plaid = PlaidConnector()
access_token = plaid.get_access_token(token)
+ bank = None
if not frappe.db.exists("Bank", response["institution"]["name"]):
try:
@@ -44,7 +51,6 @@
bank.insert()
except Exception:
frappe.throw(frappe.get_traceback())
-
else:
bank = frappe.get_doc("Bank", response["institution"]["name"])
bank.plaid_access_token = access_token
@@ -52,6 +58,7 @@
return bank
+
@frappe.whitelist()
def add_bank_accounts(response, bank, company):
try:
@@ -92,9 +99,8 @@
new_account.insert()
result.append(new_account.name)
-
except frappe.UniqueValidationError:
- frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(new_account.account_name))
+ frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
except Exception:
frappe.throw(frappe.get_traceback())
@@ -103,6 +109,7 @@
return result
+
def add_account_type(account_type):
try:
frappe.get_doc({
@@ -122,10 +129,11 @@
except Exception:
frappe.throw(frappe.get_traceback())
+
@frappe.whitelist()
def sync_transactions(bank, bank_account):
- '''Sync transactions based on the last integration date as the start date, after the sync is completed
- add the transaction date of the oldest transaction as the last integration date'''
+ """Sync transactions based on the last integration date as the start date, after sync is completed
+ add the transaction date of the oldest transaction as the last integration date."""
last_transaction_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
if last_transaction_date:
start_date = formatdate(last_transaction_date, "YYYY-MM-dd")
@@ -147,10 +155,10 @@
len(result), bank_account, start_date, end_date))
frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date)
-
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
+
def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
access_token = None
@@ -168,6 +176,7 @@
return transactions
+
def new_bank_transaction(transaction):
result = []
@@ -182,8 +191,8 @@
status = "Pending" if transaction["pending"] == "True" else "Settled"
+ tags = []
try:
- tags = []
tags += transaction["category"]
tags += ["Plaid Cat. {}".format(transaction["category_id"])]
except KeyError:
@@ -216,6 +225,7 @@
return result
+
def automatic_synchronization():
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
@@ -223,4 +233,8 @@
plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
for plaid_account in plaid_accounts:
- frappe.enqueue("erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions", bank=plaid_account.bank, bank_account=plaid_account.name)
+ frappe.enqueue(
+ "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
+ bank=plaid_account.bank,
+ bank_account=plaid_account.name
+ )
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
index 1a063d6..3c90637 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
@@ -1,14 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-from __future__ import unicode_literals
-import unittest
-import frappe
-from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import plaid_configuration, add_account_type, add_account_subtype, new_bank_transaction, add_bank_accounts
import json
-from frappe.utils.response import json_handler
+import unittest
+
+import frappe
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
+from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import (
+ add_account_subtype, add_account_type, add_bank_accounts,
+ new_bank_transaction, get_plaid_configuration)
+from frappe.utils.response import json_handler
+
class TestPlaidSettings(unittest.TestCase):
def setUp(self):
@@ -31,7 +34,7 @@
def test_plaid_disabled(self):
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
- self.assertTrue(plaid_configuration() == "disabled")
+ self.assertTrue(get_plaid_configuration() == "disabled")
def test_add_account_type(self):
add_account_type("brokerage")
@@ -64,7 +67,7 @@
'mask': '0000',
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'name': 'Plaid Checking'
- }],
+ }],
'institution': {
'institution_id': 'ins_6',
'name': 'Citi'
@@ -100,7 +103,7 @@
'mask': '0000',
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'name': 'Plaid Checking'
- }],
+ }],
'institution': {
'institution_id': 'ins_6',
'name': 'Citi'
@@ -152,4 +155,4 @@
new_bank_transaction(transactions)
- self.assertTrue(len(frappe.get_all("Bank Transaction")) == 1)
\ No newline at end of file
+ self.assertTrue(len(frappe.get_all("Bank Transaction")) == 1)
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
index 5339c99..2e10751 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
@@ -258,8 +258,8 @@
}
],
"issingle": 1,
- "modified": "2020-05-28 12:32:11.384757",
- "modified_by": "umair@erpnext.com",
+ "modified": "2020-09-18 17:26:09.703215",
+ "modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Shopify Settings",
"owner": "Administrator",
diff --git a/erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json b/erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json
index 9faa5b2..cf54e82 100644
--- a/erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json
+++ b/erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json
@@ -117,12 +117,12 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-05-08 13:45:57.226530",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Schedule Time Slot",
"name_case": "",
- "owner": "rmehta@gmail.com",
+ "owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js
index 8036c7d..f1634c1 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.js
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.js
@@ -34,10 +34,10 @@
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 @@
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 @@
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 @@
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
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json
index 2eb8014..edf1d91 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.json
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.json
@@ -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",
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py
index 865f4a1..2db7743 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.py
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.py
@@ -6,23 +6,24 @@
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 @@
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 @@
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 @@
'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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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
@@ -325,7 +349,7 @@
table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value
if item.normal_range:
- table_row += ' ' + _('Normal Range:') + item.normal_range
+ table_row += ' ' + _('Normal Range: ') + item.normal_range
table_row += ' ' + comment
elif doc.descriptive_test_items:
@@ -356,7 +380,7 @@
medical_record.save(ignore_permissions = True)
def delete_lab_test_from_medical_record(self):
- medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name= %s', (self.name))
+ medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name=%s', (self.name))
if medical_record_id and medical_record_id[0][0]:
frappe.delete_doc('Patient Medical Record', medical_record_id[0][0])
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test_list.js b/erpnext/healthcare/doctype/lab_test/lab_test_list.js
index b7f157c..7b5b9d9 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test_list.js
+++ b/erpnext/healthcare/doctype/lab_test/lab_test_list.js
@@ -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') {
- return [__('Approved'), 'green', 'status, = ,Approved'];
- }
- if (doc.status == 'Rejected') {
+ if (doc.status === 'Approved') {
+ return [__('Approved'), 'green', 'status, =, Approved'];
+ } 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 @@
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 @@
}
}
],
- 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',
diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.py b/erpnext/healthcare/doctype/lab_test/test_lab_test.py
index 4131ad9..79ab8a4 100644
--- a/erpnext/healthcare/doctype/lab_test/test_lab_test.py
+++ b/erpnext/healthcare/doctype/lab_test/test_lab_test.py
@@ -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
diff --git a/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json
index beea7a3..2767f7e 100644
--- a/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json
+++ b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json
@@ -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",
diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json
index db64297..c3fc842 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json
@@ -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",
diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
index 6f0d08c..543dee2 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
@@ -15,7 +15,8 @@
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 @@
# 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 @@
'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
+ 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):
@@ -101,9 +104,9 @@
'include_item_in_manufacturing': 0,
'show_in_website': 0,
'is_pro_applicable': 0,
- 'disabled': 0 if doc.is_billable and not doc.disabled else doc.disabled,
+ 'disabled': 0 if doc.is_billable and not doc.disabled else doc.disabled,
'stock_uom': uom
- }).insert(ignore_permissions = True, ignore_mandatory = True)
+ }).insert(ignore_permissions=True, ignore_mandatory=True)
# Insert item price
if doc.is_billable and doc.lab_test_rate != 0.0:
@@ -123,7 +126,7 @@
'price_list': price_list_name,
'item_code': item,
'price_list_rate': item_price
- }).insert(ignore_permissions = True, ignore_mandatory = True)
+ }).insert(ignore_permissions=True, ignore_mandatory=True)
@frappe.whitelist()
def change_test_code_from_template(lab_test_code, doc):
@@ -132,8 +135,8 @@
if frappe.db.exists({'doctype': 'Item', 'item_code': lab_test_code}):
frappe.throw(_('Lab Test Item {0} already exist').format(lab_test_code))
else:
- rename_doc('Item', doc.name, lab_test_code, ignore_permissions = True)
+ rename_doc('Item', doc.name, lab_test_code, ignore_permissions=True)
frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_code', lab_test_code)
frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_name', lab_test_code)
- rename_doc('Lab Test Template', doc.name, lab_test_code, ignore_permissions = True)
+ rename_doc('Lab Test Template', doc.name, lab_test_code, ignore_permissions=True)
return lab_test_code
diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py b/erpnext/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py
new file mode 100644
index 0000000..94dfeea
--- /dev/null
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py
@@ -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']
+ }
+ ]
+ }
diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js b/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js
index a3417eb..08fc2cd 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js
@@ -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']]
};
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 512fb48..e685b20 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -6,7 +6,7 @@
import frappe
from frappe.model.document import Document
import json
-from frappe.utils import getdate, get_time
+from frappe.utils import getdate, get_time, flt
from frappe.model.mapper import get_mapped_doc
from frappe import _
import datetime
@@ -45,7 +45,7 @@
def validate_overlaps(self):
end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) \
- + datetime.timedelta(minutes=float(self.duration))
+ + datetime.timedelta(minutes=flt(self.duration))
overlaps = frappe.db.sql("""
select
diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
index e5a5e4c..aa85a23 100644
--- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
+++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
@@ -34,7 +34,7 @@
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_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 @@
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')
diff --git a/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.json b/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.json
index cff100c..a21825e 100644
--- a/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.json
+++ b/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.json
@@ -44,11 +44,11 @@
}
],
"links": [],
- "modified": "2020-01-31 12:21:45.975488",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Practitioner Schedule",
- "owner": "rmehta@gmail.com",
+ "owner": "Administrator",
"permissions": [
{
"create": 1,
diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.js b/erpnext/healthcare/doctype/sample_collection/sample_collection.js
index 2f5278b..0390391 100644
--- a/erpnext/healthcare/doctype/sample_collection/sample_collection.js
+++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.js
@@ -3,29 +3,29 @@
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
},
callback: function (data) {
var age = null;
- if(data.message.dob){
+ 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 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)';
};
diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json
index 016cfbc..83383e3 100644
--- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json
+++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.json
@@ -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",
diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.py b/erpnext/healthcare/doctype/sample_collection/sample_collection.py
index 2c64320..461f809 100644
--- a/erpnext/healthcare/doctype/sample_collection/sample_collection.py
+++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.py
@@ -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'))
diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.js b/erpnext/healthcare/report/lab_test_report/lab_test_report.js
index 3128f81..7754e2e 100644
--- a/erpnext/healthcare/report/lab_test_report/lab_test_report.js
+++ b/erpnext/healthcare/report/lab_test_report/lab_test_report.js
@@ -4,29 +4,54 @@
frappe.query_reports["Lab Test Report"] = {
"filters": [
{
- "fieldname":"from_date",
+ "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",
+ "fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
- "default": frappe.datetime.now_date()
+ "default": frappe.datetime.now_date(),
+ "reqd": 1
},
{
- "fieldname":"patient",
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "default": frappe.defaults.get_default("Company"),
+ "options": "Company"
+ },
+ {
+ "fieldname": "template",
+ "label": __("Lab Test Template"),
+ "fieldtype": "Link",
+ "options": "Lab Test Template"
+ },
+ {
+ "fieldname": "patient",
"label": __("Patient"),
"fieldtype": "Link",
"options": "Patient"
},
{
- "fieldname":"department",
+ "fieldname": "department",
"label": __("Medical Department"),
"fieldtype": "Link",
"options": "Medical Department"
+ },
+ {
+ "fieldname": "status",
+ "label": __("Status"),
+ "fieldtype": "Select",
+ "options": "\nCompleted\nApproved\nRejected"
+ },
+ {
+ "fieldname": "invoiced",
+ "label": __("Invoiced"),
+ "fieldtype": "Check"
}
]
};
diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.json b/erpnext/healthcare/report/lab_test_report/lab_test_report.json
index 30e5a5f..aeb4289 100644
--- a/erpnext/healthcare/report/lab_test_report/lab_test_report.json
+++ b/erpnext/healthcare/report/lab_test_report/lab_test_report.json
@@ -1,30 +1,31 @@
{
- "add_total_row": 1,
- "creation": "2013-04-23 18:15:29",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 1,
- "is_standard": "Yes",
- "modified": "2018-08-06 11:41:50.218737",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "Lab Test Report",
- "owner": "Administrator",
- "prepared_report": 0,
- "ref_doctype": "Lab Test",
- "report_name": "Lab Test Report",
- "report_type": "Script Report",
+ "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": "2020-07-30 18:53:20.102873",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Lab Test Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Lab Test",
+ "report_name": "Lab Test Report",
+ "report_type": "Script Report",
"roles": [
{
"role": "Laboratory User"
- },
+ },
{
"role": "Nursing User"
- },
+ },
{
"role": "LabTest Approver"
- },
+ },
{
"role": "Healthcare Administrator"
}
diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.py b/erpnext/healthcare/report/lab_test_report/lab_test_report.py
index 17f25fa..2e59bed 100644
--- a/erpnext/healthcare/report/lab_test_report/lab_test_report.py
+++ b/erpnext/healthcare/report/lab_test_report/lab_test_report.py
@@ -8,51 +8,204 @@
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',
+ }
+ ]
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 463ad6c..f8b6be7 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -282,6 +282,11 @@
]
scheduler_events = {
+ "cron": {
+ "0/30 * * * *": [
+ "erpnext.utilities.doctype.video.video.update_youtube_data",
+ ]
+ },
"all": [
"erpnext.projects.doctype.project.project.project_status_update_reminder",
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder",
diff --git a/erpnext/hr/doctype/appraisal/appraisal.json b/erpnext/hr/doctype/appraisal/appraisal.json
index 4f6da97..91201d4 100644
--- a/erpnext/hr/doctype/appraisal/appraisal.json
+++ b/erpnext/hr/doctype/appraisal/appraisal.json
@@ -696,11 +696,11 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2019-01-30 11:28:08.401459",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Appraisal",
- "owner": "ashwini@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"amend": 0,
diff --git a/erpnext/hr/doctype/appraisal_goal/appraisal_goal.json b/erpnext/hr/doctype/appraisal_goal/appraisal_goal.json
index f22969b..b8ec5a8 100644
--- a/erpnext/hr/doctype/appraisal_goal/appraisal_goal.json
+++ b/erpnext/hr/doctype/appraisal_goal/appraisal_goal.json
@@ -207,11 +207,11 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-07-11 03:27:57.897071",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Appraisal Goal",
- "owner": "ashwini@webnotestech.com",
+ "owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
diff --git a/erpnext/hr/doctype/appraisal_template/appraisal_template.json b/erpnext/hr/doctype/appraisal_template/appraisal_template.json
index ac6e400..7329c35 100644
--- a/erpnext/hr/doctype/appraisal_template/appraisal_template.json
+++ b/erpnext/hr/doctype/appraisal_template/appraisal_template.json
@@ -113,11 +113,11 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-12-13 12:37:56.937023",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Appraisal Template",
- "owner": "ashwini@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"amend": 0,
diff --git a/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.json b/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.json
index 34ea5d8..2858419 100644
--- a/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.json
+++ b/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.json
@@ -78,11 +78,11 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-07-11 03:27:57.979215",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Appraisal Template Goal",
- "owner": "ashwini@webnotestech.com",
+ "owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
diff --git a/erpnext/hr/doctype/attendance/attendance.json b/erpnext/hr/doctype/attendance/attendance.json
index a656a7e..134098f 100644
--- a/erpnext/hr/doctype/attendance/attendance.json
+++ b/erpnext/hr/doctype/attendance/attendance.json
@@ -205,11 +205,11 @@
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-29 13:51:37.177231",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Attendance",
- "owner": "ashwini@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"cancel": 1,
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index 45b7060..373b940 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -98,7 +98,8 @@
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
}
diff --git a/erpnext/hr/doctype/attendance/attendance_calendar.js b/erpnext/hr/doctype/attendance/attendance_calendar.js
index 104f09d..4566489 100644
--- a/erpnext/hr/doctype/attendance/attendance_calendar.js
+++ b/erpnext/hr/doctype/attendance/attendance_calendar.js
@@ -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',
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json
index fa28470..e3e6e80 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.json
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.json
@@ -371,12 +371,12 @@
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-06-15 12:43:04.099803",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",
"name_case": "Title Case",
- "owner": "harshada@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"amend": 1,
diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
index 3cce50e..70a48f9 100644
--- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
+++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json
@@ -120,11 +120,11 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-05-11 18:54:35.601592",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim Detail",
- "owner": "harshada@webnotestech.com",
+ "owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json
index cf2cbb4..02ab4cb 100644
--- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json
+++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json
@@ -48,11 +48,11 @@
"icon": "fa fa-flag",
"idx": 1,
"links": [],
- "modified": "2019-12-11 13:38:59.534034",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim Type",
- "owner": "harshada@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"create": 1,
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index f8b7334..2c385e8 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -103,7 +103,7 @@
"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
}
diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.json b/erpnext/hr/doctype/upload_attendance/upload_attendance.json
index 16b7a83..a1451fd 100644
--- a/erpnext/hr/doctype/upload_attendance/upload_attendance.json
+++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.json
@@ -229,11 +229,11 @@
"issingle": 1,
"istable": 0,
"max_attachments": 1,
- "modified": "2017-11-14 12:51:34.980103",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "HR",
"name": "Upload Attendance",
- "owner": "harshada@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"amend": 0,
diff --git a/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json b/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json
index ae0d492..b1e421d 100644
--- a/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json
+++ b/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json
@@ -40,8 +40,8 @@
"mapping_name": "Company to Hub Company",
"mapping_type": "Push",
"migration_id_field": "hub_sync_id",
- "modified": "2018-02-14 15:57:05.571142",
- "modified_by": "achilles@erpnext.com",
+ "modified": "2020-09-18 17:26:09.703215",
+ "modified_by": "Administrator",
"name": "Company to Hub Company",
"owner": "Administrator",
"page_length": 10,
diff --git a/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json b/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json
index d2af755..d11abeb 100644
--- a/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json
+++ b/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json
@@ -21,10 +21,10 @@
"mapping_name": "Hub Message to Lead",
"mapping_type": "Pull",
"migration_id_field": "hub_sync_id",
- "modified": "2018-02-14 15:57:05.606597",
- "modified_by": "achilles@erpnext.com",
+ "modified": "2020-09-18 17:26:09.703215",
+ "modified_by": "Administrator",
"name": "Hub Message to Lead",
- "owner": "frappetest@gmail.com",
+ "owner": "Administrator",
"page_length": 10,
"remote_objectname": "Hub Message",
"remote_primary_key": "name"
diff --git a/erpnext/hub_node/doctype/hub_user/hub_user.json b/erpnext/hub_node/doctype/hub_user/hub_user.json
index a660022..f51ffb4 100644
--- a/erpnext/hub_node/doctype/hub_user/hub_user.json
+++ b/erpnext/hub_node/doctype/hub_user/hub_user.json
@@ -121,8 +121,8 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-09-01 13:56:07.816894",
- "modified_by": "netchamp@rawcoderz.com",
+ "modified": "2020-09-18 17:26:09.703215",
+ "modified_by": "Administrator",
"module": "Hub Node",
"name": "Hub User",
"name_case": "",
diff --git a/erpnext/hub_node/doctype/hub_users/hub_users.json b/erpnext/hub_node/doctype/hub_users/hub_users.json
index 2027e72..d42f3fd 100644
--- a/erpnext/hub_node/doctype/hub_users/hub_users.json
+++ b/erpnext/hub_node/doctype/hub_users/hub_users.json
@@ -54,12 +54,12 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-03-06 04:41:17.916243",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Hub Node",
"name": "Hub Users",
"name_case": "",
- "owner": "test1@example.com",
+ "owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
diff --git a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json
index 4b49f19..e784f68 100644
--- a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json
+++ b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json
@@ -352,12 +352,12 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2019-02-01 14:21:16.729848",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Hub Node",
"name": "Marketplace Settings",
"name_case": "",
- "owner": "netchamp@rawcoderz.com",
+ "owner": "Administrator",
"permissions": [
{
"amend": 0,
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index e2e27dd..d1b7589 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -240,15 +240,19 @@
unpledge_request = create_loan_security_unpledge(security_map, pledge_doc.loan,
pledge_doc.company, pledge_doc.applicant_type, pledge_doc.applicant)
- if approve:
- unpledge_request.status = 'Approved'
-
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:
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 5faf80e..5a4a19a 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -199,10 +199,9 @@
"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)'])
- self.assertEquals(flt(amounts[0], 2),flt(accrued_interest_amount, 2))
+ self.assertEquals(flt(amount, 2),flt(accrued_interest_amount, 2))
self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
loan.load_from_db()
@@ -318,6 +317,11 @@
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",
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index 1d3fa71..2d959bf 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -213,7 +213,8 @@
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
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 7d83e32..97dbc44 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -13,6 +13,7 @@
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 @@
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 @@
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
@@ -148,8 +172,6 @@
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 self.repayment_details:
- self.repayment_details[-1].paid_interest_amount += unaccrued_interest
if interest_paid:
self.principal_amount_paid += interest_paid
@@ -305,7 +327,7 @@
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'):
+ 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
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index a87d832..b3eb600 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -20,9 +20,6 @@
self.update_loan_status(cancel=1)
self.db_set('status', 'Requested')
- def on_submit(self):
- self.approve()
-
def validate_duplicate_securities(self):
security_list = []
for d in self.securities:
diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py
index cd3cf7e..0fa9686 100644
--- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py
@@ -36,6 +36,8 @@
loan_process.submit()
+ return loan_process.name
+
def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=None, loan=None):
if not term_loan_accrual_pending(posting_date or nowdate()):
@@ -49,6 +51,8 @@
loan_process.submit()
+ return loan_process.name
+
def term_loan_accrual_pending(date):
pending_accrual = frappe.db.get_value('Repayment Schedule', {
'payment_date': ('<=', date),
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
index 3788bc3..606d22f 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
@@ -813,7 +813,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-21 14:44:33.670332",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Schedule",
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
index 1192568..32bfa0e 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
@@ -1001,11 +1001,11 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2020-07-15 14:44:44.911402",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit",
- "owner": "ashwini@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"amend": 1,
diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
index 84dc72c..467441d 100644
--- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
+++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
@@ -125,11 +125,11 @@
],
"idx": 1,
"istable": 1,
- "modified": "2019-10-03 14:55:52.786805",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit Purpose",
- "owner": "ashwini@webnotestech.com",
+ "owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
diff --git a/erpnext/non_profit/doctype/donor/donor.json b/erpnext/non_profit/doctype/donor/donor.json
index 7e24dac..9639265 100644
--- a/erpnext/non_profit/doctype/donor/donor.json
+++ b/erpnext/non_profit/doctype/donor/donor.json
@@ -1,336 +1,105 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "field:email",
- "beta": 0,
- "creation": "2017-09-19 16:20:27.510196",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:email",
+ "creation": "2017-09-19 16:20:27.510196",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "donor_name",
+ "column_break_5",
+ "donor_type",
+ "email",
+ "image",
+ "address_contacts",
+ "address_html",
+ "column_break_9",
+ "contact_html"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "donor_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Donor Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "donor_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Donor Name",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_5",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "donor_type",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Donor Type",
- "length": 0,
- "no_copy": 0,
- "options": "Donor Type",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "donor_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Donor Type",
+ "options": "Donor Type",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "email",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Email",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "email",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Email",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "image",
- "fieldtype": "Attach Image",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Image",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "hidden": 1,
+ "label": "Image",
+ "no_copy": 1,
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_contacts",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Address and Contact",
- "length": 0,
- "no_copy": 0,
- "options": "fa fa-map-marker",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "depends_on": "eval:!doc.__islocal;",
+ "fieldname": "address_contacts",
+ "fieldtype": "Section Break",
+ "label": "Address and Contact",
+ "options": "fa fa-map-marker"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_html",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Address HTML",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "address_html",
+ "fieldtype": "HTML",
+ "label": "Address HTML"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_9",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_html",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact HTML",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "contact_html",
+ "fieldtype": "HTML",
+ "label": "Contact HTML"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_field": "image",
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-01-22 15:53:35.059946",
- "modified_by": "Administrator",
- "module": "Non Profit",
- "name": "Donor",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "image_field": "image",
+ "links": [],
+ "modified": "2020-09-16 23:46:04.083274",
+ "modified_by": "Administrator",
+ "module": "Non Profit",
+ "name": "Donor",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 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": "Non Profit Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Non Profit Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Non Profit",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "donor_name",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "restrict_to_domain": "Non Profit",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "donor_name",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json
index 77cdb94..992ef16 100644
--- a/erpnext/non_profit/doctype/member/member.json
+++ b/erpnext/non_profit/doctype/member/member.json
@@ -111,6 +111,7 @@
"options": "Supplier"
},
{
+ "depends_on": "eval:!doc.__islocal;",
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"label": "Address and Contact",
@@ -177,7 +178,7 @@
],
"image_field": "image",
"links": [],
- "modified": "2020-08-06 10:06:01.153564",
+ "modified": "2020-09-16 23:44:13.596948",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Member",
diff --git a/erpnext/non_profit/doctype/membership/membership.json b/erpnext/non_profit/doctype/membership/membership.json
index 95bb3a5..7f21896 100644
--- a/erpnext/non_profit/doctype/membership/membership.json
+++ b/erpnext/non_profit/doctype/membership/membership.json
@@ -90,9 +90,9 @@
},
{
"fieldname": "currency",
- "fieldtype": "Select",
+ "fieldtype": "Link",
"label": "Currency",
- "options": "USD\nINR"
+ "options": "Currency"
},
{
"fieldname": "amount",
@@ -126,7 +126,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-07-31 13:57:02.328995",
+ "modified": "2020-09-19 14:28:11.532696",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Membership",
@@ -161,4 +161,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/non_profit/doctype/volunteer/volunteer.json b/erpnext/non_profit/doctype/volunteer/volunteer.json
index 052b288..08b7f87 100644
--- a/erpnext/non_profit/doctype/volunteer/volunteer.json
+++ b/erpnext/non_profit/doctype/volunteer/volunteer.json
@@ -1,580 +1,148 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "field:email",
- "beta": 0,
- "creation": "2017-09-19 16:16:45.676019",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:email",
+ "creation": "2017-09-19 16:16:45.676019",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "volunteer_name",
+ "column_break_5",
+ "volunteer_type",
+ "email",
+ "image",
+ "address_contacts",
+ "address_html",
+ "column_break_9",
+ "contact_html",
+ "volunteer_availability_and_skills_details",
+ "availability",
+ "availability_timeslot",
+ "column_break_12",
+ "volunteer_skills",
+ "section_break_15",
+ "note"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "volunteer_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Volunteer Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "volunteer_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Volunteer Name",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_5",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "volunteer_type",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Volunteer Type",
- "length": 0,
- "no_copy": 0,
- "options": "Volunteer Type",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "volunteer_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Volunteer Type",
+ "options": "Volunteer Type",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "email",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Email",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "fieldname": "email",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Email",
+ "reqd": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "image",
- "fieldtype": "Attach Image",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Image",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "hidden": 1,
+ "label": "Image",
+ "no_copy": 1,
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_contacts",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Address and Contact",
- "length": 0,
- "no_copy": 0,
- "options": "fa fa-map-marker",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:!doc.__islocal;",
+ "fieldname": "address_contacts",
+ "fieldtype": "Section Break",
+ "label": "Address and Contact",
+ "options": "fa fa-map-marker"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_html",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Address HTML",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "address_html",
+ "fieldtype": "HTML",
+ "label": "Address HTML"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_9",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_html",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact HTML",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "contact_html",
+ "fieldtype": "HTML",
+ "label": "Contact HTML"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "volunteer_availability_and_skills_details",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Availability and Skills",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "volunteer_availability_and_skills_details",
+ "fieldtype": "Section Break",
+ "label": "Availability and Skills"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "availability",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Availability",
- "length": 0,
- "no_copy": 0,
- "options": "\nWeekly\nWeekdays\nWeekends",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "availability",
+ "fieldtype": "Select",
+ "label": "Availability",
+ "options": "\nWeekly\nWeekdays\nWeekends"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "availability_timeslot",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Availability Timeslot",
- "length": 0,
- "no_copy": 0,
- "options": "\nMorning\nAfternoon\nEvening\nAnytime",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "availability_timeslot",
+ "fieldtype": "Select",
+ "label": "Availability Timeslot",
+ "options": "\nMorning\nAfternoon\nEvening\nAnytime"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_12",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "volunteer_skills",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Volunteer Skills",
- "length": 0,
- "no_copy": 0,
- "options": "Volunteer Skill",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "volunteer_skills",
+ "fieldtype": "Table",
+ "label": "Volunteer Skills",
+ "options": "Volunteer Skill"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_15",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_15",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "note",
- "fieldtype": "Long Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Note",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "note",
+ "fieldtype": "Long Text",
+ "label": "Note"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_field": "image",
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-11-04 03:36:25.776211",
- "modified_by": "Administrator",
- "module": "Non Profit",
- "name": "Volunteer",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "image_field": "image",
+ "links": [],
+ "modified": "2020-09-16 23:45:15.595952",
+ "modified_by": "Administrator",
+ "module": "Non Profit",
+ "name": "Volunteer",
+ "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": "Non Profit Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Non Profit Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Non Profit",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "volunteer_name",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "quick_entry": 1,
+ "restrict_to_domain": "Non Profit",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "volunteer_name",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index aa7996e..6087ce2 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -690,6 +690,7 @@
erpnext.patches.v13_0.update_old_loans
erpnext.patches.v12_0.set_serial_no_status #2020-05-21
erpnext.patches.v12_0.update_price_list_currency_in_bom
+execute:frappe.reload_doctype('Dashboard')
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25
@@ -697,7 +698,7 @@
execute:frappe.delete_doc("Report", "Department Analytics")
execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True)
erpnext.patches.v12_0.update_uom_conversion_factor
-execute:frappe.delete_doc_if_exists("Page", "pos") #29-05-2020
+erpnext.patches.v13_0.replace_pos_page_with_point_of_sale_page
erpnext.patches.v13_0.delete_old_purchase_reports
erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
erpnext.patches.v13_0.update_subscription
@@ -725,3 +726,6 @@
erpnext.patches.v13_0.drop_razorpay_payload_column
erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment
erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports
+erpnext.patches.v13_0.rename_issue_doctype_fields
+erpnext.patches.v13_0.change_default_pos_print_format
+erpnext.patches.v13_0.set_youtube_video_id
diff --git a/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py b/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py
index 8e60d33..d2bcb12 100644
--- a/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py
+++ b/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py
@@ -4,17 +4,16 @@
from __future__ import unicode_literals
import frappe
+
def execute():
frappe.reload_doc("erpnext_integrations", "doctype", "plaid_settings")
plaid_settings = frappe.get_single("Plaid Settings")
if plaid_settings.enabled:
- if not (frappe.conf.plaid_client_id and frappe.conf.plaid_env \
- and frappe.conf.plaid_public_key and frappe.conf.plaid_secret):
+ if not (frappe.conf.plaid_client_id and frappe.conf.plaid_env and frappe.conf.plaid_secret):
plaid_settings.enabled = 0
else:
plaid_settings.update({
"plaid_client_id": frappe.conf.plaid_client_id,
- "plaid_public_key": frappe.conf.plaid_public_key,
"plaid_env": frappe.conf.plaid_env,
"plaid_secret": frappe.conf.plaid_secret
})
diff --git a/erpnext/patches/v13_0/change_default_pos_print_format.py b/erpnext/patches/v13_0/change_default_pos_print_format.py
new file mode 100644
index 0000000..605a29e
--- /dev/null
+++ b/erpnext/patches/v13_0/change_default_pos_print_format.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.db.sql(
+ """UPDATE `tabPOS Profile` profile
+ SET profile.`print_format` = 'POS Invoice'
+ WHERE profile.`print_format` = 'Point of Sale'""")
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
new file mode 100644
index 0000000..5bd6596
--- /dev/null
+++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
@@ -0,0 +1,65 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+ if frappe.db.exists('DocType', 'Issue'):
+ issues = frappe.db.get_all('Issue', fields=['name', 'response_by_variance', 'resolution_by_variance', 'mins_to_first_response'],
+ order_by='creation desc')
+ frappe.reload_doc('support', 'doctype', 'issue')
+
+ # rename fields
+ rename_map = {
+ 'agreement_fulfilled': 'agreement_status',
+ 'mins_to_first_response': 'first_response_time'
+ }
+ for old, new in rename_map.items():
+ rename_field('Issue', old, new)
+
+ # change fieldtype to duration
+ count = 0
+ for entry in issues:
+ response_by_variance = convert_to_seconds(entry.response_by_variance, 'Hours')
+ resolution_by_variance = convert_to_seconds(entry.resolution_by_variance, 'Hours')
+ mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes')
+ frappe.db.set_value('Issue', entry.name, {
+ 'response_by_variance': response_by_variance,
+ 'resolution_by_variance': resolution_by_variance,
+ 'first_response_time': mins_to_first_response
+ })
+ # commit after every 100 updates
+ count += 1
+ if count%100 == 0:
+ frappe.db.commit()
+
+ if frappe.db.exists('DocType', 'Opportunity'):
+ opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc')
+ frappe.reload_doc('crm', 'doctype', 'opportunity')
+ rename_field('Opportunity', 'mins_to_first_response', 'first_response_time')
+
+ # change fieldtype to duration
+ count = 0
+ for entry in opportunities:
+ mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes')
+ frappe.db.set_value('Opportunity', entry.name, 'first_response_time', mins_to_first_response)
+ # commit after every 100 updates
+ count += 1
+ if count%100 == 0:
+ frappe.db.commit()
+
+ # renamed reports from "Minutes to First Response for Issues" to "First Response Time for Issues". Same for Opportunity
+ for report in ['Minutes to First Response for Issues', 'Minutes to First Response for Opportunity']:
+ if frappe.db.exists('Report', report):
+ frappe.delete_doc('Report', report)
+
+
+def convert_to_seconds(value, unit):
+ seconds = 0
+ if unit == 'Hours':
+ seconds = value * 3600
+ if unit == 'Minutes':
+ seconds = value * 60
+ return seconds
diff --git a/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py b/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py
new file mode 100644
index 0000000..390e217
--- /dev/null
+++ b/erpnext/patches/v13_0/replace_pos_page_with_point_of_sale_page.py
@@ -0,0 +1,6 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ if frappe.db.exists("Page", "point-of-sale"):
+ frappe.rename_doc("Page", "pos", "point-of-sale", 1, 1)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/set_youtube_video_id.py b/erpnext/patches/v13_0/set_youtube_video_id.py
new file mode 100644
index 0000000..c3b49eb
--- /dev/null
+++ b/erpnext/patches/v13_0/set_youtube_video_id.py
@@ -0,0 +1,10 @@
+from __future__ import unicode_literals
+import frappe
+from erpnext.utilities.doctype.video.video import get_id_from_url
+
+def execute():
+ frappe.reload_doc("utilities", "doctype","video")
+
+ for video in frappe.get_all("Video", fields=["name", "url", "youtube_video_id"]):
+ if video.url and not video.youtube_video_id:
+ frappe.db.set_value("Video", video.name, "youtube_video_id", get_id_from_url(video.url))
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index ef174bd..e3dc907 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -5,8 +5,8 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
-from frappe import _
-from frappe.utils import getdate, date_diff
+from frappe import _, bold
+from frappe.utils import getdate, date_diff, comma_and, formatdate
class AdditionalSalary(Document):
@@ -22,9 +22,37 @@
def validate(self):
self.validate_dates()
+ self.validate_recurring_additional_salary_overlap()
if self.amount < 0:
frappe.throw(_("Amount should not be less than zero."))
+ def validate_recurring_additional_salary_overlap(self):
+ if self.is_recurring:
+ additional_salaries = frappe.db.sql("""
+ SELECT
+ name
+ FROM `tabAdditional Salary`
+ WHERE
+ employee=%s
+ AND name <> %s
+ AND docstatus=1
+ AND is_recurring=1
+ AND salary_component = %s
+ AND to_date >= %s
+ AND from_date <= %s""",
+ (self.employee, self.name, self.salary_component, self.from_date, self.to_date), as_dict = 1)
+
+ additional_salaries = [salary.name for salary in additional_salaries]
+
+ if additional_salaries and len(additional_salaries):
+ frappe.throw(_("Additional Salary: {0} already exist for Salary Component: {1} for period {2} and {3}").format(
+ bold(comma_and(additional_salaries)),
+ bold(self.salary_component),
+ bold(formatdate(self.from_date)),
+ bold(formatdate(self.to_date)
+ )))
+
+
def validate_dates(self):
date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index 9eef16b..9ba4cdc 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -13,13 +13,15 @@
for f in fields:
doctype = f.get_link_doctype()
- # apply enable/disable filter
+ # apply enable/disable/show_in_website filter
meta = frappe.get_meta(doctype)
filters = {}
if meta.has_field('enabled'):
filters['enabled'] = 1
if meta.has_field('disabled'):
filters['disabled'] = 0
+ if meta.has_field('show_in_website'):
+ filters['show_in_website'] = 1
values = [d.name for d in frappe.get_all(doctype, filters)]
filter_data.append([f, values])
diff --git a/erpnext/projects/doctype/dependent_task/dependent_task.json b/erpnext/projects/doctype/dependent_task/dependent_task.json
index e00a2b2..858a554 100644
--- a/erpnext/projects/doctype/dependent_task/dependent_task.json
+++ b/erpnext/projects/doctype/dependent_task/dependent_task.json
@@ -48,8 +48,8 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-10-26 17:37:25.638190",
- "modified_by": "rohitw1991@gmail.com",
+ "modified": "2020-09-18 17:26:09.703215",
+ "modified_by": "Administrator",
"module": "Projects",
"name": "Dependent Task",
"name_case": "",
diff --git a/erpnext/public/js/conf.js b/erpnext/public/js/conf.js
index 2af9140..eb709e5 100644
--- a/erpnext/public/js/conf.js
+++ b/erpnext/public/js/conf.js
@@ -11,7 +11,9 @@
"Territory": "Selling",
"Sales Person": "Selling",
"Sales Partner": "Selling",
- "Brand": "Stock"
+ "Brand": "Stock",
+ "Maintenance Schedule": "Support",
+ "Maintenance Visit": "Support"
});
$.extend(frappe.breadcrumbs.module_map, {
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index f4eaad5..6e97d81 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -120,7 +120,7 @@
var get_payment_mode_account = function(frm, mode_of_payment, callback) {
if(!frm.doc.company) {
- frappe.throw(__("Please select the Company first"));
+ frappe.throw({message:__("Please select a Company first."), title: __("Mandatory")});
}
if(!mode_of_payment) {
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 6951539..be30086 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -595,7 +595,7 @@
$.each(actual_taxes_dict, function(key, value) {
if (value) total_actual_tax += value;
});
-
+
return flt(this.frm.doc.grand_total - total_actual_tax, precision("grand_total"));
}
},
@@ -673,23 +673,14 @@
);
}
- frappe.db.get_value('Sales Invoice Payment', {'parent': this.frm.doc.pos_profile, 'default': 1},
- ['mode_of_payment', 'account', 'type'], (value) => {
- if (this.frm.is_dirty()) {
- frappe.model.clear_table(this.frm.doc, 'payments');
- if (value) {
- let row = frappe.model.add_child(this.frm.doc, 'Sales Invoice Payment', 'payments');
- row.mode_of_payment = value.mode_of_payment;
- row.type = value.type;
- row.account = value.account;
- row.default = 1;
- row.amount = total_amount_to_pay;
- } else {
- this.frm.set_value('is_pos', 1);
- }
- this.frm.refresh_fields();
- }
- }, 'Sales Invoice');
+ this.frm.doc.payments.find(pay => {
+ if (pay.default) {
+ pay.amount = total_amount_to_pay;
+ } else {
+ pay.amount = 0.0
+ }
+ });
+ this.frm.refresh_fields();
this.calculate_paid_amount();
},
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 792235f..3391179 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -417,7 +417,7 @@
var taxes_and_charges_field = frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges",
me.frm.doc.name);
- if (!this.frm.doc.taxes_and_charges && this.frm.doc.taxes) {
+ if (!this.frm.doc.taxes_and_charges && this.frm.doc.taxes && this.frm.doc.taxes.length > 0) {
return;
}
diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js
index 91cbbf4..4a9d1e3 100644
--- a/erpnext/public/js/education/lms/quiz.js
+++ b/erpnext/public/js/education/lms/quiz.js
@@ -140,7 +140,7 @@
make_question() {
let question_wrapper = document.createElement('h5');
question_wrapper.classList.add('mt-3');
- question_wrapper.innerText = this.question;
+ question_wrapper.innerHTML = this.question;
this.wrapper.appendChild(question_wrapper);
}
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index bcab0d8..87982f1 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -467,6 +467,33 @@
disabled: 0,
label: __('Item Code')
}, {
+ fieldtype:'Link',
+ fieldname:'uom',
+ options: 'UOM',
+ read_only: 0,
+ label: __('UOM'),
+ reqd: 1,
+ onchange: function () {
+ frappe.call({
+ method: "erpnext.stock.get_item_details.get_conversion_factor",
+ args: { item_code: this.doc.item_code, uom: this.value },
+ callback: r => {
+ if(!r.exc) {
+ if (this.doc.conversion_factor == r.message.conversion_factor) return;
+
+ const docname = this.doc.docname;
+ dialog.fields_dict.trans_items.df.data.some(doc => {
+ if (doc.docname == docname) {
+ doc.conversion_factor = r.message.conversion_factor;
+ dialog.fields_dict.trans_items.grid.refresh();
+ return true;
+ }
+ })
+ }
+ }
+ });
+ }
+ }, {
fieldtype:'Float',
fieldname:"qty",
default: 0,
@@ -546,6 +573,7 @@
"conversion_factor": d.conversion_factor,
"qty": d.qty,
"rate": d.rate,
+ "uom": d.uom
});
this.data = dialog.fields_dict.trans_items.df.data;
dialog.fields_dict.trans_items.grid.refresh();
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index 0653267..44e75ae 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -224,6 +224,10 @@
party = frm.doc.party_name;
}
+ if (!frm.doc.company) {
+ frappe.throw(__("Kindly select the company first"));
+ }
+
frappe.call({
method: "erpnext.accounts.party.set_taxes",
args: {
@@ -292,4 +296,4 @@
} else {
frappe.msgprint(__("Select company first"));
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/support/report/minutes_to_first_response_for_issues/__init__.py b/erpnext/regional/germany/utils/__init__.py
similarity index 100%
copy from erpnext/support/report/minutes_to_first_response_for_issues/__init__.py
copy to erpnext/regional/germany/utils/__init__.py
diff --git a/erpnext/crm/report/minutes_to_first_response_for_opportunity/__init__.py b/erpnext/regional/germany/utils/datev/__init__.py
similarity index 100%
copy from erpnext/crm/report/minutes_to_first_response_for_opportunity/__init__.py
copy to erpnext/regional/germany/utils/datev/__init__.py
diff --git a/erpnext/regional/report/datev/datev_constants.py b/erpnext/regional/germany/utils/datev/datev_constants.py
similarity index 89%
rename from erpnext/regional/report/datev/datev_constants.py
rename to erpnext/regional/germany/utils/datev/datev_constants.py
index e063703..63f9a77 100644
--- a/erpnext/regional/report/datev/datev_constants.py
+++ b/erpnext/regional/germany/utils/datev/datev_constants.py
@@ -460,80 +460,8 @@
"Sprach-ID"
]
-QUERY_REPORT_COLUMNS = [
- {
- "label": "Umsatz (ohne Soll/Haben-Kz)",
- "fieldname": "Umsatz (ohne Soll/Haben-Kz)",
- "fieldtype": "Currency",
- "width": 100
- },
- {
- "label": "Soll/Haben-Kennzeichen",
- "fieldname": "Soll/Haben-Kennzeichen",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": "Konto",
- "fieldname": "Konto",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": "Gegenkonto (ohne BU-Schlüssel)",
- "fieldname": "Gegenkonto (ohne BU-Schlüssel)",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": "Belegdatum",
- "fieldname": "Belegdatum",
- "fieldtype": "Date",
- "width": 100
- },
- {
- "label": "Belegfeld 1",
- "fieldname": "Belegfeld 1",
- "fieldtype": "Data",
- "width": 150
- },
- {
- "label": "Buchungstext",
- "fieldname": "Buchungstext",
- "fieldtype": "Text",
- "width": 300
- },
- {
- "label": "Beleginfo - Art 1",
- "fieldname": "Beleginfo - Art 1",
- "fieldtype": "Link",
- "options": "DocType",
- "width": 100
- },
- {
- "label": "Beleginfo - Inhalt 1",
- "fieldname": "Beleginfo - Inhalt 1",
- "fieldtype": "Dynamic Link",
- "options": "Beleginfo - Art 1",
- "width": 150
- },
- {
- "label": "Beleginfo - Art 2",
- "fieldname": "Beleginfo - Art 2",
- "fieldtype": "Link",
- "options": "DocType",
- "width": 100
- },
- {
- "label": "Beleginfo - Inhalt 2",
- "fieldname": "Beleginfo - Inhalt 2",
- "fieldtype": "Dynamic Link",
- "options": "Beleginfo - Art 2",
- "width": 150
- }
-]
-
class DataCategory():
+
"""Field of the CSV Header."""
DEBTORS_CREDITORS = "16"
@@ -542,6 +470,7 @@
POSTING_TEXT_CONSTANTS = "67"
class FormatName():
+
"""Field of the CSV Header, corresponds to DataCategory."""
DEBTORS_CREDITORS = "Debitoren/Kreditoren"
diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py
new file mode 100644
index 0000000..aae734f
--- /dev/null
+++ b/erpnext/regional/germany/utils/datev/datev_csv.py
@@ -0,0 +1,174 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import datetime
+import zipfile
+from csv import QUOTE_NONNUMERIC
+from six import BytesIO
+
+import six
+import frappe
+import pandas as pd
+from frappe import _
+from .datev_constants import DataCategory
+
+
+def get_datev_csv(data, filters, csv_class):
+ """
+ Fill in missing columns and return a CSV in DATEV Format.
+
+ For automatic processing, DATEV requires the first line of the CSV file to
+ hold meta data such as the length of account numbers oder the category of
+ the data.
+
+ Arguments:
+ data -- array of dictionaries
+ filters -- dict
+ csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS
+ """
+ empty_df = pd.DataFrame(columns=csv_class.COLUMNS)
+ data_df = pd.DataFrame.from_records(data)
+ result = empty_df.append(data_df, sort=True)
+
+ if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS:
+ result['Belegdatum'] = pd.to_datetime(result['Belegdatum'])
+
+ if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES:
+ result['Sprach-ID'] = 'de-DE'
+
+ data = result.to_csv(
+ # Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035
+ sep=str(';'),
+ # European decimal seperator
+ decimal=',',
+ # Windows "ANSI" encoding
+ encoding='latin_1',
+ # format date as DDMM
+ date_format='%d%m',
+ # Windows line terminator
+ line_terminator='\r\n',
+ # Do not number rows
+ index=False,
+ # Use all columns defined above
+ columns=csv_class.COLUMNS,
+ # Quote most fields, even currency values with "," separator
+ quoting=QUOTE_NONNUMERIC
+ )
+
+ if not six.PY2:
+ data = data.encode('latin_1')
+
+ header = get_header(filters, csv_class)
+ header = ';'.join(header).encode('latin_1')
+
+ # 1st Row: Header with meta data
+ # 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here.
+ # 3rd - nth Row: Data (Nutzdaten)
+ return header + b'\r\n' + data
+
+
+def get_header(filters, csv_class):
+ description = filters.get('voucher_type', csv_class.FORMAT_NAME)
+ company = filters.get('company')
+ datev_settings = frappe.get_doc('DATEV Settings', {'client': company})
+ default_currency = frappe.get_value('Company', company, 'default_currency')
+ coa = frappe.get_value('Company', company, 'chart_of_accounts')
+ coa_short_code = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '')
+
+ header = [
+ # DATEV format
+ # "DTVF" = created by DATEV software,
+ # "EXTF" = created by other software
+ '"EXTF"',
+ # version of the DATEV format
+ # 141 = 1.41,
+ # 510 = 5.10,
+ # 720 = 7.20
+ '700',
+ csv_class.DATA_CATEGORY,
+ '"%s"' % csv_class.FORMAT_NAME,
+ # Format version (regarding format name)
+ csv_class.FORMAT_VERSION,
+ # Generated on
+ datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '000',
+ # Imported on -- stays empty
+ '',
+ # Origin. Any two symbols, will be replaced by "SV" on import.
+ '"EN"',
+ # I = Exported by
+ '"%s"' % frappe.session.user,
+ # J = Imported by -- stays empty
+ '',
+ # K = Tax consultant number (Beraternummer)
+ datev_settings.get('consultant_number', '0000000'),
+ # L = Tax client number (Mandantennummer)
+ datev_settings.get('client_number', '00000'),
+ # M = Start of the fiscal year (Wirtschaftsjahresbeginn)
+ frappe.utils.formatdate(frappe.defaults.get_user_default('year_start_date'), 'yyyyMMdd'),
+ # N = Length of account numbers (Sachkontenlänge)
+ datev_settings.get('account_number_length', '4'),
+ # O = Transaction batch start date (YYYYMMDD)
+ frappe.utils.formatdate(filters.get('from_date'), 'yyyyMMdd') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
+ # P = Transaction batch end date (YYYYMMDD)
+ frappe.utils.formatdate(filters.get('to_date'), 'yyyyMMdd') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
+ # Q = Description (for example, "Sales Invoice") Max. 30 chars
+ '"{}"'.format(_(description)) if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
+ # R = Diktatkürzel
+ '',
+ # S = Buchungstyp
+ # 1 = Transaction batch (Finanzbuchführung),
+ # 2 = Annual financial statement (Jahresabschluss)
+ '1' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
+ # T = Rechnungslegungszweck
+ # 0 oder leer = vom Rechnungslegungszweck unabhängig
+ # 50 = Handelsrecht
+ # 30 = Steuerrecht
+ # 64 = IFRS
+ # 40 = Kalkulatorik
+ # 11 = Reserviert
+ # 12 = Reserviert
+ '0' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
+ # U = Festschreibung
+ # TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1"
+ '0',
+ # V = Default currency, for example, "EUR"
+ '"%s"' % default_currency if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
+ # reserviert
+ '',
+ # Derivatskennzeichen
+ '',
+ # reserviert
+ '',
+ # reserviert
+ '',
+ # SKR
+ '"%s"' % coa_short_code,
+ # Branchen-Lösungs-ID
+ '',
+ # reserviert
+ '',
+ # reserviert
+ '',
+ # Anwendungsinformation (Verarbeitungskennzeichen der abgebenden Anwendung)
+ ''
+ ]
+ return header
+
+
+def download_csv_files_as_zip(csv_data_list):
+ """
+ Put CSV files in a zip archive and send that to the client.
+
+ Params:
+ csv_data_list -- list of dicts [{'file_name': 'EXTF_Buchunsstapel.zip', 'csv_data': get_datev_csv()}]
+ """
+ zip_buffer = BytesIO()
+
+ datev_zip = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
+ for csv_file in csv_data_list:
+ datev_zip.writestr(csv_file.get('file_name'), csv_file.get('csv_data'))
+ datev_zip.close()
+
+ frappe.response['filecontent'] = zip_buffer.getvalue()
+ frappe.response['filename'] = 'DATEV.zip'
+ frappe.response['type'] = 'binary'
diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
index 7fec94e..dd818e6 100644
--- a/erpnext/regional/report/datev/datev.py
+++ b/erpnext/regional/report/datev/datev.py
@@ -9,31 +9,91 @@
"""
from __future__ import unicode_literals
-import datetime
import json
-import zipfile
-import six
import frappe
-import pandas as pd
-
from frappe import _
-from csv import QUOTE_NONNUMERIC
-from six import BytesIO
from six import string_types
-from .datev_constants import DataCategory
-from .datev_constants import Transactions
-from .datev_constants import DebtorsCreditors
-from .datev_constants import AccountNames
-from .datev_constants import QUERY_REPORT_COLUMNS
+from erpnext.regional.germany.utils.datev.datev_csv import download_csv_files_as_zip, get_datev_csv
+from erpnext.regional.germany.utils.datev.datev_constants import Transactions, DebtorsCreditors, AccountNames
+
+COLUMNS = [
+ {
+ "label": "Umsatz (ohne Soll/Haben-Kz)",
+ "fieldname": "Umsatz (ohne Soll/Haben-Kz)",
+ "fieldtype": "Currency",
+ "width": 100
+ },
+ {
+ "label": "Soll/Haben-Kennzeichen",
+ "fieldname": "Soll/Haben-Kennzeichen",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": "Konto",
+ "fieldname": "Konto",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": "Gegenkonto (ohne BU-Schlüssel)",
+ "fieldname": "Gegenkonto (ohne BU-Schlüssel)",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": "Belegdatum",
+ "fieldname": "Belegdatum",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": "Belegfeld 1",
+ "fieldname": "Belegfeld 1",
+ "fieldtype": "Data",
+ "width": 150
+ },
+ {
+ "label": "Buchungstext",
+ "fieldname": "Buchungstext",
+ "fieldtype": "Text",
+ "width": 300
+ },
+ {
+ "label": "Beleginfo - Art 1",
+ "fieldname": "Beleginfo - Art 1",
+ "fieldtype": "Link",
+ "options": "DocType",
+ "width": 100
+ },
+ {
+ "label": "Beleginfo - Inhalt 1",
+ "fieldname": "Beleginfo - Inhalt 1",
+ "fieldtype": "Dynamic Link",
+ "options": "Beleginfo - Art 1",
+ "width": 150
+ },
+ {
+ "label": "Beleginfo - Art 2",
+ "fieldname": "Beleginfo - Art 2",
+ "fieldtype": "Link",
+ "options": "DocType",
+ "width": 100
+ },
+ {
+ "label": "Beleginfo - Inhalt 2",
+ "fieldname": "Beleginfo - Inhalt 2",
+ "fieldtype": "Dynamic Link",
+ "options": "Beleginfo - Art 2",
+ "width": 150
+ }
+]
def execute(filters=None):
"""Entry point for frappe."""
validate(filters)
- result = get_transactions(filters, as_dict=0)
- columns = QUERY_REPORT_COLUMNS
-
- return columns, result
+ return COLUMNS, get_transactions(filters, as_dict=0)
def validate(filters):
@@ -240,146 +300,8 @@
""", filters, as_dict=1)
-def get_datev_csv(data, filters, csv_class):
- """
- Fill in missing columns and return a CSV in DATEV Format.
-
- For automatic processing, DATEV requires the first line of the CSV file to
- hold meta data such as the length of account numbers oder the category of
- the data.
-
- Arguments:
- data -- array of dictionaries
- filters -- dict
- csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS
- """
- empty_df = pd.DataFrame(columns=csv_class.COLUMNS)
- data_df = pd.DataFrame.from_records(data)
-
- result = empty_df.append(data_df, sort=True)
-
- if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS:
- result['Belegdatum'] = pd.to_datetime(result['Belegdatum'])
-
- if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES:
- result['Sprach-ID'] = 'de-DE'
-
- data = result.to_csv(
- # Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035
- sep=str(';'),
- # European decimal seperator
- decimal=',',
- # Windows "ANSI" encoding
- encoding='latin_1',
- # format date as DDMM
- date_format='%d%m',
- # Windows line terminator
- line_terminator='\r\n',
- # Do not number rows
- index=False,
- # Use all columns defined above
- columns=csv_class.COLUMNS,
- # Quote most fields, even currency values with "," separator
- quoting=QUOTE_NONNUMERIC
- )
-
- if not six.PY2:
- data = data.encode('latin_1')
-
- header = get_header(filters, csv_class)
- header = ';'.join(header).encode('latin_1')
-
- # 1st Row: Header with meta data
- # 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here.
- # 3rd - nth Row: Data (Nutzdaten)
- return header + b'\r\n' + data
-
-
-def get_header(filters, csv_class):
- description = filters.get('voucher_type', csv_class.FORMAT_NAME)
-
- header = [
- # DATEV format
- # "DTVF" = created by DATEV software,
- # "EXTF" = created by other software
- '"EXTF"',
- # version of the DATEV format
- # 141 = 1.41,
- # 510 = 5.10,
- # 720 = 7.20
- '700',
- csv_class.DATA_CATEGORY,
- '"%s"' % csv_class.FORMAT_NAME,
- # Format version (regarding format name)
- csv_class.FORMAT_VERSION,
- # Generated on
- datetime.datetime.now().strftime("%Y%m%d%H%M%S") + '000',
- # Imported on -- stays empty
- '',
- # Origin. Any two symbols, will be replaced by "SV" on import.
- '"EN"',
- # I = Exported by
- '"%s"' % frappe.session.user,
- # J = Imported by -- stays empty
- '',
- # K = Tax consultant number (Beraternummer)
- filters.get('consultant_number', '0000000'),
- # L = Tax client number (Mandantennummer)
- filters.get('client_number', '00000'),
- # M = Start of the fiscal year (Wirtschaftsjahresbeginn)
- frappe.utils.formatdate(frappe.defaults.get_user_default("year_start_date"), "yyyyMMdd"),
- # N = Length of account numbers (Sachkontenlänge)
- '%d' % filters.get('acc_len', 4),
- # O = Transaction batch start date (YYYYMMDD)
- frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd") if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
- # P = Transaction batch end date (YYYYMMDD)
- frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd") if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
- # Q = Description (for example, "Sales Invoice") Max. 30 chars
- '"{}"'.format(_(description)) if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
- # R = Diktatkürzel
- '',
- # S = Buchungstyp
- # 1 = Transaction batch (Finanzbuchführung),
- # 2 = Annual financial statement (Jahresabschluss)
- '1' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
- # T = Rechnungslegungszweck
- # 0 oder leer = vom Rechnungslegungszweck unabhängig
- # 50 = Handelsrecht
- # 30 = Steuerrecht
- # 64 = IFRS
- # 40 = Kalkulatorik
- # 11 = Reserviert
- # 12 = Reserviert
- '0' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
- # U = Festschreibung
- # TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1"
- '0',
- # V = Default currency, for example, "EUR"
- '"%s"' % filters.get('default_currency', 'EUR') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
- # reserviert
- '',
- # Derivatskennzeichen
- '',
- # reserviert
- '',
- # reserviert
- '',
- # SKR
- '"%s"' % filters.get('skr', '04'),
- # Branchen-Lösungs-ID
- '',
- # reserviert
- '',
- # reserviert
- '',
- # Anwendungsinformation (Verarbeitungskennzeichen der abgebenden Anwendung)
- ''
- ]
- return header
-
-
@frappe.whitelist()
-def download_datev_csv(filters=None):
+def download_datev_csv(filters):
"""
Provide accounting entries for download in DATEV format.
@@ -400,38 +322,26 @@
coa = frappe.get_value('Company', filters.get('company'), 'chart_of_accounts')
filters['skr'] = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '')
- # set account number length
- account_numbers = frappe.get_list('Account', fields=['account_number'], filters={'is_group': 0, 'account_number': ('!=', '')})
- filters['acc_len'] = max([len(a.account_number) for a in account_numbers])
-
- filters['consultant_number'] = frappe.get_value('DATEV Settings', filters.get('company'), 'consultant_number')
- filters['client_number'] = frappe.get_value('DATEV Settings', filters.get('company'), 'client_number')
- filters['default_currency'] = frappe.get_value('Company', filters.get('company'), 'default_currency')
-
- # This is where my zip will be written
- zip_buffer = BytesIO()
- # This is my zip file
- datev_zip = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
-
transactions = get_transactions(filters)
- transactions_csv = get_datev_csv(transactions, filters, csv_class=Transactions)
- datev_zip.writestr('EXTF_Buchungsstapel.csv', transactions_csv)
-
account_names = get_account_names(filters)
- account_names_csv = get_datev_csv(account_names, filters, csv_class=AccountNames)
- datev_zip.writestr('EXTF_Kontenbeschriftungen.csv', account_names_csv)
-
customers = get_customers(filters)
- customers_csv = get_datev_csv(customers, filters, csv_class=DebtorsCreditors)
- datev_zip.writestr('EXTF_Kunden.csv', customers_csv)
-
suppliers = get_suppliers(filters)
- suppliers_csv = get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors)
- datev_zip.writestr('EXTF_Lieferanten.csv', suppliers_csv)
-
- # You must call close() before exiting your program or essential records will not be written.
- datev_zip.close()
- frappe.response['filecontent'] = zip_buffer.getvalue()
- frappe.response['filename'] = 'DATEV.zip'
- frappe.response['type'] = 'binary'
+ download_csv_files_as_zip([
+ {
+ 'file_name': 'EXTF_Buchungsstapel.csv',
+ 'csv_data': get_datev_csv(transactions, filters, csv_class=Transactions)
+ },
+ {
+ 'file_name': 'EXTF_Kontenbeschriftungen.csv',
+ 'csv_data': get_datev_csv(account_names, filters, csv_class=AccountNames)
+ },
+ {
+ 'file_name': 'EXTF_Kunden.csv',
+ 'csv_data': get_datev_csv(customers, filters, csv_class=DebtorsCreditors)
+ },
+ {
+ 'file_name': 'EXTF_Lieferanten.csv',
+ 'csv_data': get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors)
+ },
+ ])
diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py
index eed62a8..9529923 100644
--- a/erpnext/regional/report/datev/test_datev.py
+++ b/erpnext/regional/report/datev/test_datev.py
@@ -1,32 +1,22 @@
# coding=utf-8
from __future__ import unicode_literals
-import os
-import json
import zipfile
+import frappe
from six import BytesIO
from unittest import TestCase
-
-import frappe
-from frappe.utils import getdate, today, now_datetime, cstr
-from frappe.test_runner import make_test_objects
+from frappe.utils import today, now_datetime, cstr
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
-from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
from erpnext.regional.report.datev.datev import validate
from erpnext.regional.report.datev.datev import get_transactions
from erpnext.regional.report.datev.datev import get_customers
from erpnext.regional.report.datev.datev import get_suppliers
from erpnext.regional.report.datev.datev import get_account_names
-from erpnext.regional.report.datev.datev import get_datev_csv
-from erpnext.regional.report.datev.datev import get_header
from erpnext.regional.report.datev.datev import download_datev_csv
-from erpnext.regional.report.datev.datev_constants import DataCategory
-from erpnext.regional.report.datev.datev_constants import Transactions
-from erpnext.regional.report.datev.datev_constants import DebtorsCreditors
-from erpnext.regional.report.datev.datev_constants import AccountNames
-from erpnext.regional.report.datev.datev_constants import QUERY_REPORT_COLUMNS
+from erpnext.regional.germany.utils.datev.datev_csv import get_datev_csv, get_header
+from erpnext.regional.germany.utils.datev.datev_constants import Transactions, DebtorsCreditors, AccountNames
def make_company(company_name, abbr):
if not frappe.db.exists("Company", company_name):
diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py
index 67834d1..d3509e5 100644
--- a/erpnext/regional/report/irs_1099/irs_1099.py
+++ b/erpnext/regional/report/irs_1099/irs_1099.py
@@ -16,9 +16,15 @@
def execute(filters=None):
filters = filters if isinstance(filters, _dict) else _dict(filters)
+
if not filters:
filters.setdefault('fiscal_year', get_fiscal_year(nowdate())[0])
filters.setdefault('company', frappe.db.get_default("company"))
+
+ region = frappe.db.get_value("Company", fieldname = ["country"], filters = { "name": filters.company })
+ if region != 'United States':
+ return [],[]
+
data = []
columns = get_columns()
data = frappe.db.sql("""
diff --git a/erpnext/regional/united_states/test_united_states.py b/erpnext/regional/united_states/test_united_states.py
index 688f145..ad95010 100644
--- a/erpnext/regional/united_states/test_united_states.py
+++ b/erpnext/regional/united_states/test_united_states.py
@@ -24,7 +24,7 @@
def test_irs_1099_report(self):
make_payment_entry_to_irs_1099_supplier()
- filters = frappe._dict({"fiscal_year": "_Test Fiscal Year 2016", "company": "_Test Company"})
+ filters = frappe._dict({"fiscal_year": "_Test Fiscal Year 2016", "company": "_Test Company 1"})
columns, data = execute_1099_report(filters)
print(columns, data)
expected_row = {'supplier': '_US 1099 Test Supplier',
@@ -42,10 +42,10 @@
pe = frappe.new_doc("Payment Entry")
pe.payment_type = "Pay"
- pe.company = "_Test Company"
+ pe.company = "_Test Company 1"
pe.posting_date = "2016-01-10"
- pe.paid_from = "_Test Bank USD - _TC"
- pe.paid_to = "_Test Payable USD - _TC"
+ pe.paid_from = "_Test Bank USD - _TC1"
+ pe.paid_to = "_Test Payable USD - _TC1"
pe.paid_amount = 100
pe.received_amount = 100
pe.reference_no = "For IRS 1099 testing"
diff --git a/erpnext/selling/desk_page/retail/retail.json b/erpnext/selling/desk_page/retail/retail.json
index 581e14c..c4ddf26 100644
--- a/erpnext/selling/desk_page/retail/retail.json
+++ b/erpnext/selling/desk_page/retail/retail.json
@@ -2,8 +2,18 @@
"cards": [
{
"hidden": 0,
- "label": "Retail Operations",
- "links": "[\n {\n \"description\": \"Setup default values for POS Invoices\",\n \"label\": \"Point of Sale Profile\",\n \"name\": \"POS Profile\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"POS Profile\"\n ],\n \"description\": \"Point of Sale\",\n \"label\": \"Point of Sale\",\n \"name\": \"point-of-sale\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"description\": \"Setup mode of POS (Online / Offline)\",\n \"label\": \"POS Settings\",\n \"name\": \"POS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Cashier Closing\",\n \"label\": \"Cashier Closing\",\n \"name\": \"Cashier Closing\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To make Customer based incentive schemes.\",\n \"label\": \"Loyalty Program\",\n \"name\": \"Loyalty Program\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To view logs of Loyalty Points assigned to a Customer.\",\n \"label\": \"Loyalty Point Entry\",\n \"name\": \"Loyalty Point Entry\",\n \"type\": \"doctype\"\n }\n]"
+ "label": "Settings & Configurations",
+ "links": "[\n {\n \"description\": \"Setup default values for POS Invoices\",\n \"label\": \"Point-of-Sale Profile\",\n \"name\": \"POS Profile\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"POS Settings\",\n \"name\": \"POS Settings\",\n \"type\": \"doctype\"\n }\n]"
+ },
+ {
+ "hidden": 0,
+ "label": "Loyalty Program",
+ "links": "[\n {\n \"description\": \"To make Customer based incentive schemes.\",\n \"label\": \"Loyalty Program\",\n \"name\": \"Loyalty Program\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To view logs of Loyalty Points assigned to a Customer.\",\n \"label\": \"Loyalty Point Entry\",\n \"name\": \"Loyalty Point Entry\",\n \"type\": \"doctype\"\n }\n]"
+ },
+ {
+ "hidden": 0,
+ "label": "Opening & Closing",
+ "links": "[\n {\n \"label\": \"POS Opening Entry\",\n \"name\": \"POS Opening Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"POS Closing Entry\",\n \"name\": \"POS Closing Entry\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Domains",
@@ -18,7 +28,7 @@
"idx": 0,
"is_standard": 1,
"label": "Retail",
- "modified": "2020-08-20 18:00:07.515691",
+ "modified": "2020-09-09 11:46:28.297435",
"modified_by": "Administrator",
"module": "Selling",
"name": "Retail",
@@ -28,25 +38,10 @@
"restrict_to_domain": "Retail",
"shortcuts": [
{
- "color": "#9deca2",
"doc_view": "",
- "format": "{} Active",
- "label": "Point of Sale Profile",
- "link_to": "POS Profile",
- "stats_filter": "{\n \"disabled\": 0\n}",
- "type": "DocType"
- },
- {
- "doc_view": "",
- "label": "Point of Sale",
+ "label": "Point Of Sale",
"link_to": "point-of-sale",
"type": "Page"
- },
- {
- "doc_view": "",
- "label": "POS Settings",
- "link_to": "POS Settings",
- "type": "DocType"
}
]
}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/industry_type/industry_type.json b/erpnext/selling/doctype/industry_type/industry_type.json
index f4fcae4..6c49f0f 100644
--- a/erpnext/selling/doctype/industry_type/industry_type.json
+++ b/erpnext/selling/doctype/industry_type/industry_type.json
@@ -49,11 +49,11 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-07-25 05:24:25.881361",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Selling",
"name": "Industry Type",
- "owner": "harshada@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"amend": 0,
diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.json b/erpnext/selling/doctype/product_bundle/product_bundle.json
index b63fb4b..56155fb 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.json
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.json
@@ -238,8 +238,8 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-10-18 14:23:06.538568",
- "modified_by": "tundebabzy@gmail.com",
+ "modified": "2020-09-18 17:26:09.703215",
+ "modified_by": "Administrator",
"module": "Selling",
"name": "Product Bundle",
"owner": "Administrator",
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index accaa9c..735b071 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -318,7 +318,7 @@
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 20)
- def test_add_new_item_in_update_child_qty_rate(self):
+ def test_update_child_adding_new_item(self):
so = make_sales_order(item_code= "_Test Item", qty=4)
create_dn_against_so(so.name, 4)
make_sales_invoice(so.name)
@@ -338,7 +338,7 @@
self.assertEqual(so.get("items")[-1].amount, 1400)
self.assertEqual(so.status, 'To Deliver and Bill')
- def test_remove_item_in_update_child_qty_rate(self):
+ def test_update_child_removing_item(self):
so = make_sales_order(**{
"item_list": [{
"item_code": '_Test Item',
@@ -381,7 +381,7 @@
self.assertEqual(so.status, 'To Deliver and Bill')
- def test_update_child_qty_rate(self):
+ def test_update_child(self):
so = make_sales_order(item_code= "_Test Item", qty=4)
create_dn_against_so(so.name, 4)
make_sales_invoice(so.name)
@@ -402,7 +402,7 @@
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
- def test_update_child_qty_rate_perm(self):
+ def test_update_child_perm(self):
so = make_sales_order(item_code= "_Test Item", qty=4)
user = 'test@example.com'
@@ -417,9 +417,44 @@
# add new item
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+ test_user.remove_roles("Accounts User")
frappe.set_user("Administrator")
+
+ def test_update_child_qty_rate_with_workflow(self):
+ from frappe.model.workflow import apply_workflow
- def test_update_child_qty_rate_product_bundle(self):
+ workflow = make_sales_order_workflow()
+ so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1)
+ apply_workflow(so, 'Approve')
+
+ frappe.set_user("Administrator")
+ user = 'test@example.com'
+ test_user = frappe.get_doc('User', user)
+ test_user.add_roles("Sales User", "Test Junior Approver")
+ frappe.set_user(user)
+
+ # user shouldn't be able to edit since grand_total will become > 200 if qty is doubled
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 150, 'qty' : 2, 'docname': so.items[0].name}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Sales Order', trans_item, so.name)
+
+ frappe.set_user("Administrator")
+ user2 = 'test2@example.com'
+ test_user2 = frappe.get_doc('User', user2)
+ test_user2.add_roles("Sales User", "Test Approver")
+ frappe.set_user(user2)
+
+ # Test Approver is allowed to edit with grand_total > 200
+ update_child_qty_rate("Sales Order", trans_item, so.name)
+ so.reload()
+ self.assertEqual(so.items[0].qty, 2)
+
+ frappe.set_user("Administrator")
+ test_user.remove_roles("Sales User", "Test Junior Approver", "Test Approver")
+ test_user2.remove_roles("Sales User", "Test Junior Approver", "Test Approver")
+ workflow.is_active = 0
+ workflow.save()
+
+ def test_update_child_product_bundle(self):
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Product Bundle Item"):
bundle_item = make_item("_Product Bundle Item", {"is_stock_item": 0})
@@ -439,6 +474,20 @@
so.reload()
self.assertEqual(so.packed_items[0].qty, 4)
+ # test uom and conversion factor change
+ update_uom_conv_factor = json.dumps([{
+ 'item_code': so.get("items")[0].item_code,
+ 'rate': so.get("items")[0].rate,
+ 'qty': so.get("items")[0].qty,
+ 'uom': "_Test UOM 1",
+ 'conversion_factor': 2,
+ 'docname': so.get("items")[0].name
+ }])
+ update_child_qty_rate('Sales Order', update_uom_conv_factor, so.name)
+
+ so.reload()
+ self.assertEqual(so.packed_items[0].qty, 8)
+
def test_warehouse_user(self):
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
@@ -973,3 +1022,37 @@
"reserved_qty"))
test_dependencies = ["Currency Exchange"]
+
+def make_sales_order_workflow():
+ if frappe.db.exists('Workflow', 'SO Test Workflow'):
+ doc = frappe.get_doc("Workflow", "SO Test Workflow")
+ doc.set("is_active", 1)
+ doc.save()
+ return doc
+
+ frappe.get_doc(dict(doctype='Role', role_name='Test Junior Approver')).insert(ignore_if_duplicate=True)
+ frappe.get_doc(dict(doctype='Role', role_name='Test Approver')).insert(ignore_if_duplicate=True)
+ frappe.db.commit()
+ frappe.cache().hdel('roles', frappe.session.user)
+
+ workflow = frappe.get_doc({
+ "doctype": "Workflow",
+ "workflow_name": "SO Test Workflow",
+ "document_type": "Sales Order",
+ "workflow_state_field": "workflow_state",
+ "is_active": 1,
+ "send_email_alert": 0,
+ })
+ workflow.append('states', dict( state = 'Pending', allow_edit = 'All' ))
+ workflow.append('states', dict( state = 'Approved', allow_edit = 'Test Approver', doc_status = 1 ))
+ workflow.append('transitions', dict(
+ state = 'Pending', action = 'Approve', next_state = 'Approved', allowed = 'Test Junior Approver', allow_self_approval = 1,
+ condition = 'doc.grand_total < 200'
+ ))
+ workflow.append('transitions', dict(
+ state = 'Pending', action = 'Approve', next_state = 'Approved', allowed = 'Test Approver', allow_self_approval = 1,
+ condition = 'doc.grand_total > 200'
+ ))
+ workflow.insert(ignore_permissions=True)
+
+ return workflow
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index 2ce0b27..8d4ac78 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -9,7 +9,7 @@
title: __('Point of Sale'),
single_column: true
});
- // online
+
wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
window.cur_pos = wrapper.pos;
};
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index ae5471b..5018254 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -8,7 +8,7 @@
{% include "erpnext/selling/page/point_of_sale/pos_past_order_summary.js" %}
erpnext.PointOfSale.Controller = class {
- constructor(wrapper) {
+ constructor(wrapper) {
this.wrapper = $(wrapper).find('.layout-main-section');
this.page = wrapper.page;
@@ -36,7 +36,7 @@
const table_fields = [
{ fieldname: "mode_of_payment", fieldtype: "Link", in_list_view: 1, label: "Mode of Payment", options: "Mode of Payment", reqd: 1 },
{ fieldname: "opening_amount", fieldtype: "Currency", default: 0, in_list_view: 1, label: "Opening Amount",
- options: "company:company_currency", reqd: 1 }
+ options: "company:company_currency" }
];
const dialog = new frappe.ui.Dialog({
@@ -51,29 +51,16 @@
options: 'POS Profile', fieldname: 'pos_profile', reqd: 1,
onchange: () => {
const pos_profile = dialog.fields_dict.pos_profile.get_value();
- const company = dialog.fields_dict.company.get_value();
- const user = frappe.session.user
- if (!pos_profile || !company || !user) return;
+ if (!pos_profile) return;
- // auto fetch last closing entry's balance details
- frappe.db.get_list("POS Closing Entry", {
- filters: { company, pos_profile, user },
- limit: 1,
- order_by: 'period_end_date desc'
- }).then((res) => {
- if (!res.length) return;
- const pos_closing_entry = res[0];
- frappe.db.get_doc("POS Closing Entry", pos_closing_entry.name).then(({ payment_reconciliation }) => {
- dialog.fields_dict.balance_details.df.data = [];
- payment_reconciliation.forEach(pay => {
- const { mode_of_payment } = pay;
- dialog.fields_dict.balance_details.df.data.push({
- mode_of_payment: mode_of_payment
- });
- });
- dialog.fields_dict.balance_details.grid.refresh();
+ frappe.db.get_doc("POS Profile", pos_profile).then(doc => {
+ dialog.fields_dict.balance_details.df.data = [];
+ doc.payments.forEach(pay => {
+ const { mode_of_payment } = pay;
+ dialog.fields_dict.balance_details.df.data.push({ mode_of_payment });
});
+ dialog.fields_dict.balance_details.grid.refresh();
});
}
},
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index eadeb8f..724b60b 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -1,36 +1,36 @@
erpnext.PointOfSale.ItemCart = class {
- constructor({ wrapper, events }) {
+ constructor({ wrapper, events }) {
this.wrapper = wrapper;
this.events = events;
- this.customer_info = undefined;
-
- this.init_component();
- }
-
- init_component() {
- this.prepare_dom();
- this.init_child_components();
+ this.customer_info = undefined;
+
+ this.init_component();
+ }
+
+ init_component() {
+ this.prepare_dom();
+ this.init_child_components();
this.bind_events();
this.attach_shortcuts();
- }
+ }
- prepare_dom() {
+ prepare_dom() {
this.wrapper.append(
- `<section class="col-span-4 flex flex-col shadow rounded item-cart bg-white mx-h-70 h-100"></section>`
- )
+ `<section class="col-span-4 flex flex-col shadow rounded item-cart bg-white mx-h-70 h-100"></section>`
+ )
- this.$component = this.wrapper.find('.item-cart');
- }
+ this.$component = this.wrapper.find('.item-cart');
+ }
- init_child_components() {
- this.init_customer_selector();
- this.init_cart_components();
- }
+ init_child_components() {
+ this.init_customer_selector();
+ this.init_cart_components();
+ }
- init_customer_selector() {
+ init_customer_selector() {
this.$component.append(
- `<div class="customer-section rounded flex flex-col m-8 mb-0"></div>`
- )
+ `<div class="customer-section rounded flex flex-col m-8 mb-0"></div>`
+ )
this.$customer_section = this.$component.find('.customer-section');
}
@@ -41,9 +41,9 @@
this.make_customer_selector();
this.customer_field.set_focus();
}
-
- init_cart_components() {
- this.$component.append(
+
+ init_cart_components() {
+ this.$component.append(
`<div class="cart-container flex flex-col items-center rounded flex-1 relative">
<div class="absolute flex flex-col p-8 pt-0 w-full h-full">
<div class="flex text-grey cart-header pt-2 pb-2 p-4 mt-2 mb-2 w-full f-shrink-0">
@@ -55,23 +55,23 @@
<div class="cart-totals-section flex flex-col w-full mt-4 f-shrink-0"></div>
<div class="numpad-section flex flex-col mt-4 d-none w-full p-8 pt-0 pb-0 f-shrink-0"></div>
</div>
- </div>`
- );
+ </div>`
+ );
this.$cart_container = this.$component.find('.cart-container');
this.make_cart_totals_section();
this.make_cart_items_section();
- this.make_cart_numpad();
- }
+ this.make_cart_numpad();
+ }
- make_cart_items_section() {
- this.$cart_header = this.$component.find('.cart-header');
- this.$cart_items_wrapper = this.$component.find('.cart-items-section');
+ make_cart_items_section() {
+ this.$cart_header = this.$component.find('.cart-header');
+ this.$cart_items_wrapper = this.$component.find('.cart-items-section');
this.make_no_items_placeholder();
- }
-
- make_no_items_placeholder() {
+ }
+
+ make_no_items_placeholder() {
this.$cart_header.addClass('d-none');
this.$cart_items_wrapper.html(
`<div class="no-item-wrapper flex items-center h-18">
@@ -81,8 +81,8 @@
this.$cart_items_wrapper.addClass('mt-4 border-grey border-dashed');
}
- make_cart_totals_section() {
- this.$totals_section = this.$component.find('.cart-totals-section');
+ make_cart_totals_section() {
+ this.$totals_section = this.$component.find('.cart-totals-section');
this.$totals_section.append(
`<div class="add-discount flex items-center pt-4 pb-4 pr-4 pl-4 text-grey pointer no-select d-none">
@@ -116,9 +116,9 @@
)
this.$add_discount_elem = this.$component.find(".add-discount");
- }
-
- make_cart_numpad() {
+ }
+
+ make_cart_numpad() {
this.$numpad_section = this.$component.find('.numpad-section');
this.number_pad = new erpnext.PointOfSale.NumberPad({
@@ -155,9 +155,9 @@
Checkout
</div>`
)
- }
-
- bind_events() {
+ }
+
+ bind_events() {
const me = this;
this.$customer_section.on('click', '.add-remove-customer', function (e) {
const customer_info_is_visible = me.$cart_container.hasClass('d-none');
@@ -381,8 +381,8 @@
`
);
}
-
- update_customer_section() {
+
+ update_customer_section() {
const { customer, email_id='', mobile_no='', image } = this.customer_info || {};
if (customer) {
@@ -403,7 +403,7 @@
</div>`
);
} else {
- // reset customer selector
+ // reset customer selector
this.reset_customer_selector();
}
@@ -430,9 +430,9 @@
</div>`
}
}
- }
-
- update_totals_section(frm) {
+ }
+
+ update_totals_section(frm) {
if (!frm) frm = this.events.get_frm();
this.render_net_total(frm.doc.base_net_total);
@@ -440,9 +440,9 @@
const taxes = frm.doc.taxes.map(t => { return { description: t.description, rate: t.rate }})
this.render_taxes(frm.doc.base_total_taxes_and_charges, taxes);
- }
-
- render_net_total(value) {
+ }
+
+ render_net_total(value) {
const currency = this.events.get_frm().doc.currency;
this.$totals_section.find('.net-total').html(
`<div class="flex flex-col">
@@ -454,9 +454,9 @@
)
this.$numpad_section.find('.numpad-net-total').html(`Net Total: <span class="text-bold">${format_currency(value, currency)}</span>`)
- }
-
- render_grand_total(value) {
+ }
+
+ render_grand_total(value) {
const currency = this.events.get_frm().doc.currency;
this.$totals_section.find('.grand-total').html(
`<div class="flex flex-col">
@@ -495,20 +495,20 @@
} else {
this.$totals_section.find('.taxes').html('')
}
- }
+ }
- get_cart_item({ item_code, batch_no, uom }) {
+ get_cart_item({ item_code, batch_no, uom }) {
const batch_attr = `[data-batch-no="${escape(batch_no)}"]`;
const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
const uom_attr = `[data-uom=${escape(uom)}]`;
- const item_selector = batch_no ?
- `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`;
-
- return this.$cart_items_wrapper.find(item_selector);
- }
-
- update_item_html(item, remove_item) {
+ const item_selector = batch_no ?
+ `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`;
+
+ return this.$cart_items_wrapper.find(item_selector);
+ }
+
+ update_item_html(item, remove_item) {
const $item = this.get_cart_item(item);
if (remove_item) {
@@ -524,33 +524,33 @@
const no_of_cart_items = this.$cart_items_wrapper.children().length;
no_of_cart_items > 0 && this.highlight_checkout_btn(no_of_cart_items > 0);
-
+
this.update_empty_cart_section(no_of_cart_items);
}
-
- render_cart_item(item_data, $item_to_update) {
+
+ render_cart_item(item_data, $item_to_update) {
const currency = this.events.get_frm().doc.currency;
const me = this;
- if (!$item_to_update.length) {
- this.$cart_items_wrapper.append(
- `<div class="cart-item-wrapper flex items-center h-18 pr-4 pl-4 rounded border-grey pointer no-select"
+ if (!$item_to_update.length) {
+ this.$cart_items_wrapper.append(
+ `<div class="cart-item-wrapper flex items-center h-18 pr-4 pl-4 rounded border-grey pointer no-select"
data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
data-batch-no="${escape(item_data.batch_no || '')}">
- </div>`
- )
- $item_to_update = this.get_cart_item(item_data);
- }
+ </div>`
+ )
+ $item_to_update = this.get_cart_item(item_data);
+ }
$item_to_update.html(
`<div class="flex flex-col flex-1 f-shrink-1 overflow-hidden whitespace-nowrap">
- <div class="text-md text-dark-grey text-bold">
- ${item_data.item_name}
- </div>
- ${get_description_html()}
- </div>
- ${get_rate_discount_html()}
- </div>`
+ <div class="text-md text-dark-grey text-bold">
+ ${item_data.item_name}
+ </div>
+ ${get_description_html()}
+ </div>
+ ${get_rate_discount_html()}
+ </div>`
)
set_dynamic_rate_header_width();
@@ -572,7 +572,7 @@
me.$cart_header.find(".rate-list-header").css("width", max_width);
me.$cart_items_wrapper.find(".rate-col").css("width", max_width);
}
-
+
function get_rate_discount_html() {
if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) {
return `
@@ -625,7 +625,7 @@
$item_to_update.attr(`data-${selector}`, value);
}
- toggle_checkout_btn(show_checkout) {
+ toggle_checkout_btn(show_checkout) {
if (show_checkout) {
this.$totals_section.find('.checkout-btn').removeClass('d-none');
this.$totals_section.find('.edit-cart-btn').addClass('d-none');
@@ -635,7 +635,7 @@
}
}
- highlight_checkout_btn(toggle) {
+ highlight_checkout_btn(toggle) {
const has_primary_class = this.$totals_section.find('.checkout-btn').hasClass('bg-primary');
if (toggle && !has_primary_class) {
this.$totals_section.find('.checkout-btn').addClass('bg-primary text-white text-lg');
@@ -643,8 +643,8 @@
this.$totals_section.find('.checkout-btn').removeClass('bg-primary text-white text-lg');
}
}
-
- update_empty_cart_section(no_of_cart_items) {
+
+ update_empty_cart_section(no_of_cart_items) {
const $no_item_element = this.$cart_items_wrapper.find('.no-item-wrapper');
// if cart has items and no item is present
@@ -652,27 +652,27 @@
&& this.$cart_items_wrapper.removeClass('mt-4 border-grey border-dashed') && this.$cart_header.removeClass('d-none');
no_of_cart_items === 0 && !$no_item_element.length && this.make_no_items_placeholder();
- }
-
- on_numpad_event($btn) {
+ }
+
+ on_numpad_event($btn) {
const current_action = $btn.attr('data-button-value');
const action_is_field_edit = ['qty', 'discount_percentage', 'rate'].includes(current_action);
this.highlight_numpad_btn($btn, current_action);
- const action_is_pressed_twice = this.prev_action === current_action;
- const first_click_event = !this.prev_action;
- const field_to_edit_changed = this.prev_action && this.prev_action != current_action;
+ const action_is_pressed_twice = this.prev_action === current_action;
+ const first_click_event = !this.prev_action;
+ const field_to_edit_changed = this.prev_action && this.prev_action != current_action;
if (action_is_field_edit) {
if (first_click_event || field_to_edit_changed) {
- this.prev_action = current_action;
+ this.prev_action = current_action;
} else if (action_is_pressed_twice) {
this.prev_action = undefined;
}
- this.numpad_value = '';
-
+ this.numpad_value = '';
+
} else if (current_action === 'checkout') {
this.prev_action = undefined;
this.toggle_item_highlight();
@@ -688,7 +688,7 @@
this.numpad_value = this.numpad_value || 0;
}
- const first_click_event_is_not_field_edit = !action_is_field_edit && first_click_event;
+ const first_click_event_is_not_field_edit = !action_is_field_edit && first_click_event;
if (first_click_event_is_not_field_edit) {
frappe.show_alert({
@@ -708,34 +708,34 @@
this.numpad_value = current_action;
}
- this.events.numpad_event(this.numpad_value, this.prev_action);
- }
-
- highlight_numpad_btn($btn, curr_action) {
- const curr_action_is_highlighted = $btn.hasClass('shadow-inner');
- const curr_action_is_action = ['qty', 'discount_percentage', 'rate', 'done'].includes(curr_action);
+ this.events.numpad_event(this.numpad_value, this.prev_action);
+ }
+
+ highlight_numpad_btn($btn, curr_action) {
+ const curr_action_is_highlighted = $btn.hasClass('shadow-inner');
+ const curr_action_is_action = ['qty', 'discount_percentage', 'rate', 'done'].includes(curr_action);
- if (!curr_action_is_highlighted) {
- $btn.addClass('shadow-inner bg-selected');
- }
- if (this.prev_action === curr_action && curr_action_is_highlighted) {
- // if Qty is pressed twice
- $btn.removeClass('shadow-inner bg-selected');
- }
- if (this.prev_action && this.prev_action !== curr_action && curr_action_is_action) {
- // Order: Qty -> Rate then remove Qty highlight
- const prev_btn = $(`[data-button-value='${this.prev_action}']`);
- prev_btn.removeClass('shadow-inner bg-selected');
- }
- if (!curr_action_is_action || curr_action === 'done') {
- // if numbers are clicked
- setTimeout(() => {
- $btn.removeClass('shadow-inner bg-selected');
- }, 100);
- }
- }
+ if (!curr_action_is_highlighted) {
+ $btn.addClass('shadow-inner bg-selected');
+ }
+ if (this.prev_action === curr_action && curr_action_is_highlighted) {
+ // if Qty is pressed twice
+ $btn.removeClass('shadow-inner bg-selected');
+ }
+ if (this.prev_action && this.prev_action !== curr_action && curr_action_is_action) {
+ // Order: Qty -> Rate then remove Qty highlight
+ const prev_btn = $(`[data-button-value='${this.prev_action}']`);
+ prev_btn.removeClass('shadow-inner bg-selected');
+ }
+ if (!curr_action_is_action || curr_action === 'done') {
+ // if numbers are clicked
+ setTimeout(() => {
+ $btn.removeClass('shadow-inner bg-selected');
+ }, 100);
+ }
+ }
- toggle_numpad(show) {
+ toggle_numpad(show) {
if (show) {
this.$totals_section.addClass('d-none');
this.$numpad_section.removeClass('d-none');
@@ -946,6 +946,6 @@
toggle_component(show) {
show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
- }
-
+ }
+
}
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index 86a1be9..3a5f89b 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -1,28 +1,28 @@
erpnext.PointOfSale.ItemDetails = class {
- constructor({ wrapper, events }) {
+ constructor({ wrapper, events }) {
this.wrapper = wrapper;
- this.events = events;
- this.current_item = {};
+ this.events = events;
+ this.current_item = {};
- this.init_component();
- }
+ this.init_component();
+ }
- init_component() {
- this.prepare_dom();
- this.init_child_components();
+ init_component() {
+ this.prepare_dom();
+ this.init_child_components();
this.bind_events();
this.attach_shortcuts();
- }
+ }
- prepare_dom() {
- this.wrapper.append(
- `<section class="col-span-4 flex shadow rounded item-details bg-white mx-h-70 h-100 d-none"></section>`
- )
+ prepare_dom() {
+ this.wrapper.append(
+ `<section class="col-span-4 flex shadow rounded item-details bg-white mx-h-70 h-100 d-none"></section>`
+ )
- this.$component = this.wrapper.find('.item-details');
- }
+ this.$component = this.wrapper.find('.item-details');
+ }
- init_child_components() {
+ init_child_components() {
this.$component.html(
`<div class="details-container flex flex-col p-8 rounded w-full">
<div class="flex justify-between mb-2">
@@ -49,28 +49,28 @@
this.$item_image = this.$component.find('.item-image');
this.$form_container = this.$component.find('.form-container');
this.$dicount_section = this.$component.find('.discount-section');
- }
+ }
- toggle_item_details_section(item) {
+ toggle_item_details_section(item) {
const { item_code, batch_no, uom } = this.current_item;
const item_code_is_same = item && item_code === item.item_code;
const batch_is_same = item && batch_no == item.batch_no;
const uom_is_same = item && uom === item.uom;
- this.item_has_changed = !item ? false : item_code_is_same && batch_is_same && uom_is_same ? false : true;
+ this.item_has_changed = !item ? false : item_code_is_same && batch_is_same && uom_is_same ? false : true;
- this.events.toggle_item_selector(this.item_has_changed);
+ this.events.toggle_item_selector(this.item_has_changed);
this.toggle_component(this.item_has_changed);
-
+
if (this.item_has_changed) {
- this.doctype = item.doctype;
+ this.doctype = item.doctype;
this.item_meta = frappe.get_meta(this.doctype);
this.name = item.name;
this.item_row = item;
- this.currency = this.events.get_frm().doc.currency;
-
- this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom };
-
+ this.currency = this.events.get_frm().doc.currency;
+
+ this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom };
+
this.render_dom(item);
this.render_discount_dom(item);
this.render_form(item);
@@ -102,9 +102,9 @@
this.events.remove_item_from_cart();
}
}
-
- render_dom(item) {
- let { item_code ,item_name, description, image, price_list_rate } = item;
+
+ render_dom(item) {
+ let { item_code ,item_name, description, image, price_list_rate } = item;
function get_description_html() {
if (description) {
@@ -112,8 +112,8 @@
return description;
}
return ``;
- }
-
+ }
+
this.$item_name.html(item_name);
this.$item_description.html(get_description_html());
this.$item_price.html(format_currency(price_list_rate, this.currency));
@@ -125,9 +125,9 @@
this.$item_image.html(frappe.get_abbr(item_code));
}
- }
-
- render_discount_dom(item) {
+ }
+
+ render_discount_dom(item) {
if (item.discount_percentage) {
this.$dicount_section.html(
`<div class="text-grey line-through mr-4 text-md mb-2">
@@ -141,9 +141,9 @@
} else {
this.$dicount_section.html(``)
}
- }
+ }
- render_form(item) {
+ render_form(item) {
const fields_to_display = this.get_form_fields(item);
this.$form_container.html('');
@@ -157,7 +157,7 @@
const field_meta = this.item_meta.fields.find(df => df.fieldname === fieldname);
fieldname === 'discount_percentage' ? (field_meta.label = __('Discount (%)')) : '';
const me = this;
-
+
this[`${fieldname}_control`] = frappe.ui.form.make_control({
df: {
...field_meta,
@@ -174,16 +174,16 @@
this.make_auto_serial_selection_btn(item);
this.bind_custom_control_change_event();
- }
+ }
- get_form_fields(item) {
+ get_form_fields(item) {
const fields = ['qty', 'uom', 'rate', 'price_list_rate', 'discount_percentage', 'warehouse', 'actual_qty'];
if (item.has_serial_no) fields.push('serial_no');
if (item.has_batch_no) fields.push('batch_no');
return fields;
}
- make_auto_serial_selection_btn(item) {
+ make_auto_serial_selection_btn(item) {
if (item.has_serial_no) {
this.$form_container.append(
`<div class="grid-filler no-select"></div>`
@@ -203,8 +203,8 @@
this.$form_container.find('.serial_no-control').parent().addClass('row-span-2');
}
}
-
- bind_custom_control_change_event() {
+
+ bind_custom_control_change_event() {
const me = this;
if (this.rate_control) {
this.rate_control.df.onchange = function() {
@@ -276,8 +276,8 @@
};
this.batch_no_control.df.onchange = function() {
me.events.set_value_in_current_cart_item('batch-no', this.value);
- me.events.form_updated(me.doctype, me.name, 'batch_no', this.value);
- me.current_item.batch_no = this.value;
+ me.events.form_updated(me.doctype, me.name, 'batch_no', this.value);
+ me.current_item.batch_no = this.value;
}
this.batch_no_control.refresh();
}
@@ -289,9 +289,9 @@
me.current_item.uom = this.value;
}
}
- }
-
- async auto_update_batch_no() {
+ }
+
+ async auto_update_batch_no() {
if (this.serial_no_control && this.batch_no_control) {
const selected_serial_nos = this.serial_no_control.get_value().split(`\n`).filter(s => s);
if (!selected_serial_nos.length) return;
@@ -310,9 +310,9 @@
const batch_no = Object.keys(batch_serial_map)[0];
const batch_serial_nos = batch_serial_map[batch_no].join(`\n`);
// eg. 10 selected serial no. -> 5 belongs to first batch other 5 belongs to second batch
- const serial_nos_belongs_to_other_batch = selected_serial_nos.length !== batch_serial_map[batch_no].length;
-
- const current_batch_no = this.batch_no_control.get_value();
+ const serial_nos_belongs_to_other_batch = selected_serial_nos.length !== batch_serial_map[batch_no].length;
+
+ const current_batch_no = this.batch_no_control.get_value();
current_batch_no != batch_no && await this.batch_no_control.set_value(batch_no);
if (serial_nos_belongs_to_other_batch) {
@@ -326,8 +326,8 @@
this.events.clone_new_batch_item_in_frm(batch_serial_map, this.current_item);
}
}
-
- bind_events() {
+
+ bind_events() {
this.bind_auto_serial_fetch_event();
this.bind_fields_to_numpad_fields();
@@ -345,7 +345,7 @@
});
}
- bind_fields_to_numpad_fields() {
+ bind_fields_to_numpad_fields() {
const me = this;
this.$form_container.on('click', '.input-with-feedback', function() {
const fieldname = $(this).attr('data-fieldname');
@@ -355,8 +355,8 @@
}
});
}
-
- bind_auto_serial_fetch_event() {
+
+ bind_auto_serial_fetch_event() {
this.$form_container.on('click', '.auto-fetch-btn', () => {
this.batch_no_control.set_value('');
let qty = this.qty_control.get_value();
@@ -382,7 +382,7 @@
frappe.msgprint(`Fetched only ${records_length} available serial numbers.`);
this.qty_control.set_value(records_length);
}
- numbers = auto_fetched_serial_numbers.join(`\n`);
+ numbers = auto_fetched_serial_numbers.join(`\n`);
this.serial_no_control.set_value(numbers);
});
})
@@ -390,5 +390,5 @@
toggle_component(show) {
show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
- }
+ }
}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index ee0c06d..c87b845 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -1,115 +1,115 @@
erpnext.PointOfSale.ItemSelector = class {
- constructor({ frm, wrapper, events, pos_profile }) {
+ constructor({ frm, wrapper, events, pos_profile }) {
this.wrapper = wrapper;
this.events = events;
- this.pos_profile = pos_profile;
-
- this.inti_component();
- }
-
- inti_component() {
- this.prepare_dom();
- this.make_search_bar();
- this.load_items_data();
- this.bind_events();
- this.attach_shortcuts();
- }
+ this.pos_profile = pos_profile;
+
+ this.inti_component();
+ }
+
+ inti_component() {
+ this.prepare_dom();
+ this.make_search_bar();
+ this.load_items_data();
+ this.bind_events();
+ this.attach_shortcuts();
+ }
- prepare_dom() {
+ prepare_dom() {
this.wrapper.append(
- `<section class="col-span-6 flex shadow rounded items-selector bg-white mx-h-70 h-100">
- <div class="flex flex-col rounded w-full scroll-y">
- <div class="filter-section flex p-8 pb-2 bg-white sticky z-100">
- <div class="search-field flex f-grow-3 mr-8 items-center text-grey"></div>
- <div class="item-group-field flex f-grow-1 items-center text-grey text-bold"></div>
- </div>
- <div class="flex flex-1 flex-col p-8 pt-2">
- <div class="text-grey mb-6">ALL ITEMS</div>
- <div class="items-container grid grid-cols-4 gap-8">
- </div>
- </div>
- </div>
- </section>`
- );
-
- this.$component = this.wrapper.find('.items-selector');
- }
+ `<section class="col-span-6 flex shadow rounded items-selector bg-white mx-h-70 h-100">
+ <div class="flex flex-col rounded w-full scroll-y">
+ <div class="filter-section flex p-8 pb-2 bg-white sticky z-100">
+ <div class="search-field flex f-grow-3 mr-8 items-center text-grey"></div>
+ <div class="item-group-field flex f-grow-1 items-center text-grey text-bold"></div>
+ </div>
+ <div class="flex flex-1 flex-col p-8 pt-2">
+ <div class="text-grey mb-6">ALL ITEMS</div>
+ <div class="items-container grid grid-cols-4 gap-8">
+ </div>
+ </div>
+ </div>
+ </section>`
+ );
+
+ this.$component = this.wrapper.find('.items-selector');
+ }
- async load_items_data() {
- if (!this.item_group) {
- const res = await frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name");
- this.parent_item_group = res.message.name;
- };
- if (!this.price_list) {
- const res = await frappe.db.get_value("POS Profile", this.pos_profile, "selling_price_list");
- this.price_list = res.message.selling_price_list;
- }
+ async load_items_data() {
+ if (!this.item_group) {
+ const res = await frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name");
+ this.parent_item_group = res.message.name;
+ };
+ if (!this.price_list) {
+ const res = await frappe.db.get_value("POS Profile", this.pos_profile, "selling_price_list");
+ this.price_list = res.message.selling_price_list;
+ }
- this.get_items({}).then(({message}) => {
- this.render_item_list(message.items);
- });
- }
+ this.get_items({}).then(({message}) => {
+ this.render_item_list(message.items);
+ });
+ }
- get_items({start = 0, page_length = 40, search_value=''}) {
- const price_list = this.events.get_frm().doc?.selling_price_list || this.price_list;
- let { item_group, pos_profile } = this;
+ get_items({start = 0, page_length = 40, search_value=''}) {
+ const price_list = this.events.get_frm().doc?.selling_price_list || this.price_list;
+ let { item_group, pos_profile } = this;
- !item_group && (item_group = this.parent_item_group);
-
+ !item_group && (item_group = this.parent_item_group);
+
return frappe.call({
method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items",
freeze: true,
- args: { start, page_length, price_list, item_group, search_value, pos_profile },
- });
+ args: { start, page_length, price_list, item_group, search_value, pos_profile },
+ });
}
render_item_list(items) {
- this.$items_container = this.$component.find('.items-container');
- this.$items_container.html('');
+ this.$items_container = this.$component.find('.items-container');
+ this.$items_container.html('');
- items.forEach(item => {
- const item_html = this.get_item_html(item);
- this.$items_container.append(item_html);
- })
- }
+ items.forEach(item => {
+ const item_html = this.get_item_html(item);
+ this.$items_container.append(item_html);
+ })
+ }
- get_item_html(item) {
- const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
- const indicator_color = actual_qty > 10 ? "green" : actual_qty !== 0 ? "orange" : "red";
+ get_item_html(item) {
+ const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
+ const indicator_color = actual_qty > 10 ? "green" : actual_qty !== 0 ? "orange" : "red";
- function get_item_image_html() {
- if (item_image) {
- return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
- <img class="h-full" src="${item_image}" alt="${item_image}" style="object-fit: cover;">
- </div>`
- } else {
- return `<div class="flex items-center justify-center h-32 bg-light-grey text-6xl text-grey-100">
- ${frappe.get_abbr(item.item_name)}
- </div>`
- }
- }
+ function get_item_image_html() {
+ if (item_image) {
+ return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
+ <img class="h-full" src="${item_image}" alt="${item_image}" style="object-fit: cover;">
+ </div>`
+ } else {
+ return `<div class="flex items-center justify-center h-32 bg-light-grey text-6xl text-grey-100">
+ ${frappe.get_abbr(item.item_name)}
+ </div>`
+ }
+ }
return (
- `<div class="item-wrapper rounded shadow pointer no-select" data-item-code="${escape(item.item_code)}"
- data-serial-no="${escape(serial_no)}" data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
- title="Avaiable Qty: ${actual_qty}">
- ${get_item_image_html()}
- <div class="flex items-center pr-4 pl-4 h-10 justify-between">
- <div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
- <span class="indicator ${indicator_color}"></span>
- ${frappe.ellipsis(item.item_name, 18)}
- </div>
- <div class="f-shrink-0 text-dark-grey text-bold ml-4">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
- </div>
- </div>`
- )
- }
+ `<div class="item-wrapper rounded shadow pointer no-select" data-item-code="${escape(item.item_code)}"
+ data-serial-no="${escape(serial_no)}" data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
+ title="Avaiable Qty: ${actual_qty}">
+ ${get_item_image_html()}
+ <div class="flex items-center pr-4 pl-4 h-10 justify-between">
+ <div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
+ <span class="indicator ${indicator_color}"></span>
+ ${frappe.ellipsis(item.item_name, 18)}
+ </div>
+ <div class="f-shrink-0 text-dark-grey text-bold ml-4">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
+ </div>
+ </div>`
+ )
+ }
- make_search_bar() {
- const me = this;
- this.$component.find('.search-field').html('');
- this.$component.find('.item-group-field').html('');
+ make_search_bar() {
+ const me = this;
+ this.$component.find('.search-field').html('');
+ this.$component.find('.item-group-field').html('');
this.search_field = frappe.ui.form.make_control({
df: {
@@ -119,104 +119,104 @@
},
parent: this.$component.find('.search-field'),
render_input: true,
- });
+ });
this.item_group_field = frappe.ui.form.make_control({
df: {
label: __('Item Group'),
fieldtype: 'Link',
options: 'Item Group',
- placeholder: __('Select item group'),
- onchange: function() {
- me.item_group = this.value;
- !me.item_group && (me.item_group = me.parent_item_group);
- me.filter_items();
- },
- get_query: function () {
- return {
- query: 'erpnext.selling.page.point_of_sale.point_of_sale.item_group_query',
- filters: {
- pos_profile: me.events.get_frm().doc?.pos_profile
- }
- }
- },
+ placeholder: __('Select item group'),
+ onchange: function() {
+ me.item_group = this.value;
+ !me.item_group && (me.item_group = me.parent_item_group);
+ me.filter_items();
+ },
+ get_query: function () {
+ return {
+ query: 'erpnext.selling.page.point_of_sale.point_of_sale.item_group_query',
+ filters: {
+ pos_profile: me.events.get_frm().doc?.pos_profile
+ }
+ }
+ },
},
- parent: this.$component.find('.item-group-field'),
+ parent: this.$component.find('.item-group-field'),
render_input: true,
- });
- this.search_field.toggle_label(false);
+ });
+ this.search_field.toggle_label(false);
this.item_group_field.toggle_label(false);
}
- bind_events() {
- const me = this;
- onScan.attachTo(document, {
- onScan: (sScancode) => {
- if (this.search_field && this.$component.is(':visible')) {
- this.search_field.set_focus();
- $(this.search_field.$input[0]).val(sScancode).trigger("input");
- this.barcode_scanned = true;
- }
- }
- });
+ bind_events() {
+ const me = this;
+ onScan.attachTo(document, {
+ onScan: (sScancode) => {
+ if (this.search_field && this.$component.is(':visible')) {
+ this.search_field.set_focus();
+ $(this.search_field.$input[0]).val(sScancode).trigger("input");
+ this.barcode_scanned = true;
+ }
+ }
+ });
this.$component.on('click', '.item-wrapper', function() {
const $item = $(this);
const item_code = unescape($item.attr('data-item-code'));
- let batch_no = unescape($item.attr('data-batch-no'));
- let serial_no = unescape($item.attr('data-serial-no'));
- let uom = unescape($item.attr('data-uom'));
-
- // escape(undefined) returns "undefined" then unescape returns "undefined"
- batch_no = batch_no === "undefined" ? undefined : batch_no;
- serial_no = serial_no === "undefined" ? undefined : serial_no;
- uom = uom === "undefined" ? undefined : uom;
+ let batch_no = unescape($item.attr('data-batch-no'));
+ let serial_no = unescape($item.attr('data-serial-no'));
+ let uom = unescape($item.attr('data-uom'));
+
+ // escape(undefined) returns "undefined" then unescape returns "undefined"
+ batch_no = batch_no === "undefined" ? undefined : batch_no;
+ serial_no = serial_no === "undefined" ? undefined : serial_no;
+ uom = uom === "undefined" ? undefined : uom;
- me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }});
- })
+ me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }});
+ })
- this.search_field.$input.on('input', (e) => {
+ this.search_field.$input.on('input', (e) => {
clearTimeout(this.last_search);
this.last_search = setTimeout(() => {
const search_term = e.target.value;
this.filter_items({ search_term });
}, 300);
- });
- }
+ });
+ }
- attach_shortcuts() {
- frappe.ui.keys.on("ctrl+i", () => {
- const selector_is_visible = this.$component.is(':visible');
- if (!selector_is_visible) return;
- this.search_field.set_focus();
- });
- frappe.ui.keys.on("ctrl+g", () => {
- const selector_is_visible = this.$component.is(':visible');
- if (!selector_is_visible) return;
- this.item_group_field.set_focus();
- });
- // for selecting the last filtered item on search
- frappe.ui.keys.on("enter", () => {
- const selector_is_visible = this.$component.is(':visible');
- if (!selector_is_visible || this.search_field.get_value() === "") return;
+ attach_shortcuts() {
+ frappe.ui.keys.on("ctrl+i", () => {
+ const selector_is_visible = this.$component.is(':visible');
+ if (!selector_is_visible) return;
+ this.search_field.set_focus();
+ });
+ frappe.ui.keys.on("ctrl+g", () => {
+ const selector_is_visible = this.$component.is(':visible');
+ if (!selector_is_visible) return;
+ this.item_group_field.set_focus();
+ });
+ // for selecting the last filtered item on search
+ frappe.ui.keys.on("enter", () => {
+ const selector_is_visible = this.$component.is(':visible');
+ if (!selector_is_visible || this.search_field.get_value() === "") return;
- if (this.items.length == 1) {
- this.$items_container.find(".item-wrapper").click();
- frappe.utils.play_sound("submit");
- $(this.search_field.$input[0]).val("").trigger("input");
- } else if (this.items.length == 0 && this.barcode_scanned) {
- // only show alert of barcode is scanned and enter is pressed
- frappe.show_alert({
- message: __("No items found. Scan barcode again."),
- indicator: 'orange'
- });
- frappe.utils.play_sound("error");
- this.barcode_scanned = false;
- $(this.search_field.$input[0]).val("").trigger("input");
- }
- });
- }
-
- filter_items({ search_term='' }={}) {
+ if (this.items.length == 1) {
+ this.$items_container.find(".item-wrapper").click();
+ frappe.utils.play_sound("submit");
+ $(this.search_field.$input[0]).val("").trigger("input");
+ } else if (this.items.length == 0 && this.barcode_scanned) {
+ // only show alert of barcode is scanned and enter is pressed
+ frappe.show_alert({
+ message: __("No items found. Scan barcode again."),
+ indicator: 'orange'
+ });
+ frappe.utils.play_sound("error");
+ this.barcode_scanned = false;
+ $(this.search_field.$input[0]).val("").trigger("input");
+ }
+ });
+ }
+
+ filter_items({ search_term='' }={}) {
if (search_term) {
search_term = search_term.toLowerCase();
@@ -227,39 +227,39 @@
this.items = items;
this.render_item_list(items);
return;
- }
+ }
}
this.get_items({ search_value: search_term })
- .then(({ message }) => {
- const { items, serial_no, batch_no, barcode } = message;
+ .then(({ message }) => {
+ const { items, serial_no, batch_no, barcode } = message;
if (search_term && !barcode) {
this.search_index[search_term] = items;
}
this.items = items;
- this.render_item_list(items);
- });
+ this.render_item_list(items);
+ });
}
-
- resize_selector(minimize) {
- minimize ?
- this.$component.find('.search-field').removeClass('mr-8') :
- this.$component.find('.search-field').addClass('mr-8');
+
+ resize_selector(minimize) {
+ minimize ?
+ this.$component.find('.search-field').removeClass('mr-8') :
+ this.$component.find('.search-field').addClass('mr-8');
- minimize ?
- this.$component.find('.filter-section').addClass('flex-col') :
- this.$component.find('.filter-section').removeClass('flex-col');
+ minimize ?
+ this.$component.find('.filter-section').addClass('flex-col') :
+ this.$component.find('.filter-section').removeClass('flex-col');
- minimize ?
- this.$component.removeClass('col-span-6').addClass('col-span-2') :
- this.$component.removeClass('col-span-2').addClass('col-span-6')
+ minimize ?
+ this.$component.removeClass('col-span-6').addClass('col-span-2') :
+ this.$component.removeClass('col-span-2').addClass('col-span-6')
- minimize ?
- this.$items_container.removeClass('grid-cols-4').addClass('grid-cols-1') :
- this.$items_container.removeClass('grid-cols-1').addClass('grid-cols-4')
- }
+ minimize ?
+ this.$items_container.removeClass('grid-cols-4').addClass('grid-cols-1') :
+ this.$items_container.removeClass('grid-cols-1').addClass('grid-cols-4')
+ }
- toggle_component(show) {
+ toggle_component(show) {
show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
- }
+ }
}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_number_pad.js b/erpnext/selling/page/point_of_sale/pos_number_pad.js
index 2ffc2c0..4b8e841 100644
--- a/erpnext/selling/page/point_of_sale/pos_number_pad.js
+++ b/erpnext/selling/page/point_of_sale/pos_number_pad.js
@@ -1,49 +1,48 @@
erpnext.PointOfSale.NumberPad = class {
- constructor({ wrapper, events, cols, keys, css_classes, fieldnames_map }) {
- this.wrapper = wrapper;
- this.events = events;
- this.cols = cols;
- this.keys = keys;
- this.css_classes = css_classes || [];
- this.fieldnames = fieldnames_map || {};
+ constructor({ wrapper, events, cols, keys, css_classes, fieldnames_map }) {
+ this.wrapper = wrapper;
+ this.events = events;
+ this.cols = cols;
+ this.keys = keys;
+ this.css_classes = css_classes || [];
+ this.fieldnames = fieldnames_map || {};
- this.init_component();
- }
+ this.init_component();
+ }
- init_component() {
- this.prepare_dom();
- this.bind_events();
- }
+ init_component() {
+ this.prepare_dom();
+ this.bind_events();
+ }
- prepare_dom() {
- const { cols, keys, css_classes, fieldnames } = this;
+ prepare_dom() {
+ const { cols, keys, css_classes, fieldnames } = this;
- function get_keys() {
- return keys.reduce((a, row, i) => {
- return a + row.reduce((a2, number, j) => {
- const class_to_append = css_classes && css_classes[i] ? css_classes[i][j] : '';
- const fieldname = fieldnames && fieldnames[number] ?
- fieldnames[number] :
- typeof number === 'string' ? frappe.scrub(number) : number;
-
- return a2 + `<div class="numpad-btn pointer no-select rounded ${class_to_append}
- flex items-center justify-center h-16 text-md border-grey border" data-button-value="${fieldname}">${number}</div>`
- }, '')
- }, '');
- }
+ function get_keys() {
+ return keys.reduce((a, row, i) => {
+ return a + row.reduce((a2, number, j) => {
+ const class_to_append = css_classes && css_classes[i] ? css_classes[i][j] : '';
+ const fieldname = fieldnames && fieldnames[number] ?
+ fieldnames[number] : typeof number === 'string' ? frappe.scrub(number) : number;
- this.wrapper.html(
- `<div class="grid grid-cols-${cols} gap-4">
- ${get_keys()}
- </div>`
- )
- }
+ return a2 + `<div class="numpad-btn pointer no-select rounded ${class_to_append}
+ flex items-center justify-center h-16 text-md border-grey border" data-button-value="${fieldname}">${number}</div>`
+ }, '')
+ }, '');
+ }
- bind_events() {
- const me = this;
- this.wrapper.on('click', '.numpad-btn', function() {
- const $btn = $(this);
- me.events.numpad_event($btn);
- })
- }
+ this.wrapper.html(
+ `<div class="grid grid-cols-${cols} gap-4">
+ ${get_keys()}
+ </div>`
+ )
+ }
+
+ bind_events() {
+ const me = this;
+ this.wrapper.on('click', '.numpad-btn', function() {
+ const $btn = $(this);
+ me.events.numpad_event($btn);
+ });
+ }
}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index e1c54f6..7f0cabe 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -70,13 +70,23 @@
this.$invoice_fields.append(
`<div class="invoice_detail_field ${df.fieldname}-field" data-fieldname="${df.fieldname}"></div>`
);
+ let df_events = {
+ onchange: function() { frm.set_value(this.df.fieldname, this.value); }
+ }
+ if (df.fieldtype == "Button") {
+ df_events = {
+ click: function() {
+ if (frm.script_manager.has_handlers(df.fieldname, frm.doc.doctype)) {
+ frm.script_manager.trigger(df.fieldname, frm.doc.doctype, frm.doc.docname);
+ }
+ }
+ }
+ }
this[`${df.fieldname}_field`] = frappe.ui.form.make_control({
df: {
...df,
- onchange: function() {
- frm.set_value(this.df.fieldname, this.value);
- }
+ ...df_events
},
parent: this.$invoice_fields.find(`.${df.fieldname}-field`),
render_input: true,
diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index 0a70b97..c716aa9 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -188,7 +188,7 @@
conditions += "AND so.transaction_date <= '%s'" %filters.to_date
if filters.get("item_code"):
- conditions += "AND so_item.item_code = '%s'" %frappe.db.escape(filters.item_code)
+ conditions += "AND so_item.item_code = %s" %frappe.db.escape(filters.item_code)
if filters.get("customer"):
conditions += "AND so.customer = %s" %frappe.db.escape(filters.customer)
diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.js b/erpnext/selling/report/sales_analytics/sales_analytics.js
index 80874c1..0e565a3 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.js
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.js
@@ -8,7 +8,7 @@
fieldname: "tree_type",
label: __("Tree Type"),
fieldtype: "Select",
- options: ["Customer Group","Customer","Item Group","Item","Territory","Order Type"],
+ options: ["Customer Group", "Customer", "Item Group", "Item", "Territory", "Order Type", "Project"],
default: "Customer",
reqd: 1
},
diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py
index 4d113c8..d036a1c 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.py
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.py
@@ -34,7 +34,7 @@
def get_columns(self):
self.columns = [{
- "label": _(self.filters.tree_type + " ID"),
+ "label": _(self.filters.tree_type),
"options": self.filters.tree_type if self.filters.tree_type != "Order Type" else "",
"fieldname": "entity",
"fieldtype": "Link" if self.filters.tree_type != "Order Type" else "Data",
@@ -97,6 +97,10 @@
self.get_sales_transactions_based_on_order_type()
self.get_rows_by_group()
+ elif self.filters.tree_type == "Project":
+ self.get_sales_transactions_based_on_project()
+ self.get_rows()
+
def get_sales_transactions_based_on_order_type(self):
if self.filters["value_quantity"] == 'Value':
value_field = "base_net_total"
@@ -198,6 +202,24 @@
self.get_groups()
+ def get_sales_transactions_based_on_project(self):
+ if self.filters["value_quantity"] == 'Value':
+ value_field = "base_net_total as value_field"
+ else:
+ value_field = "total_qty as value_field"
+
+ entity = "project as entity"
+
+ self.entries = frappe.get_all(self.filters.doc_type,
+ fields=[entity, value_field, self.date_field],
+ filters={
+ "docstatus": 1,
+ "company": self.filters.company,
+ "project": ["!=", ""],
+ self.date_field: ('between', [self.filters.from_date, self.filters.to_date])
+ }
+ )
+
def get_rows(self):
self.data = []
self.get_periodic_data()
@@ -205,7 +227,7 @@
for entity, period_data in iteritems(self.entity_periodic_data):
row = {
"entity": entity,
- "entity_name": self.entity_names.get(entity)
+ "entity_name": self.entity_names.get(entity) if hasattr(self, 'entity_names') else None
}
total = 0
for end_date in self.periodic_daterange:
diff --git a/erpnext/stock/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json
index 1bf81f7..2fba5fa 100644
--- a/erpnext/stock/desk_page/stock/stock.json
+++ b/erpnext/stock/desk_page/stock/stock.json
@@ -33,7 +33,7 @@
{
"hidden": 0,
"label": "Key Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Item Price\"\n ],\n \"doctype\": \"Item Price\",\n \"is_query_report\": false,\n \"label\": \"Item-wise Price List Rate\",\n \"name\": \"Item-wise Price List Rate\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Entry\"\n ],\n \"doctype\": \"Stock Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Analytics\",\n \"name\": \"Stock Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Delivery Note\"\n ],\n \"doctype\": \"Delivery Note\",\n \"is_query_report\": true,\n \"label\": \"Delivery Note Trends\",\n \"name\": \"Delivery Note Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Receipt\"\n ],\n \"doctype\": \"Purchase Receipt\",\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Analysis\",\n \"name\": \"Sales Order Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Purchase Order Analysis\",\n \"name\": \"Purchase Order Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Bin\"\n ],\n \"doctype\": \"Bin\",\n \"is_query_report\": true,\n \"label\": \"Item Shortage Report\",\n \"name\": \"Item Shortage Report\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Batch\"\n ],\n \"doctype\": \"Batch\",\n \"is_query_report\": true,\n \"label\": \"Batch-Wise Balance History\",\n \"name\": \"Batch-Wise Balance History\",\n \"type\": \"report\"\n }\n]"
+ "links": "[\n {\n \"dependencies\": [\n \"Item Price\"\n ],\n \"doctype\": \"Item Price\",\n \"is_query_report\": false,\n \"label\": \"Item-wise Price List Rate\",\n \"name\": \"Item-wise Price List Rate\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Entry\"\n ],\n \"doctype\": \"Stock Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Analytics\",\n \"name\": \"Stock Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Stock Qty vs Serial No Count\",\n \"name\": \"Stock Qty vs Serial No Count\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Delivery Note\"\n ],\n \"doctype\": \"Delivery Note\",\n \"is_query_report\": true,\n \"label\": \"Delivery Note Trends\",\n \"name\": \"Delivery Note Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Receipt\"\n ],\n \"doctype\": \"Purchase Receipt\",\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Analysis\",\n \"name\": \"Sales Order Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Purchase Order Analysis\",\n \"name\": \"Purchase Order Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Bin\"\n ],\n \"doctype\": \"Bin\",\n \"is_query_report\": true,\n \"label\": \"Item Shortage Report\",\n \"name\": \"Item Shortage Report\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Batch\"\n ],\n \"doctype\": \"Batch\",\n \"is_query_report\": true,\n \"label\": \"Batch-Wise Balance History\",\n \"name\": \"Batch-Wise Balance History\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
@@ -58,7 +58,7 @@
"idx": 0,
"is_standard": 1,
"label": "Stock",
- "modified": "2020-05-30 17:32:11.062681",
+ "modified": "2020-08-11 17:29:32.626067",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",
diff --git a/erpnext/stock/doctype/batch/batch.json b/erpnext/stock/doctype/batch/batch.json
index 1eb4577..943cb34 100644
--- a/erpnext/stock/doctype/batch/batch.json
+++ b/erpnext/stock/doctype/batch/batch.json
@@ -166,11 +166,11 @@
"idx": 1,
"image_field": "image",
"max_attachments": 5,
- "modified": "2019-10-03 22:38:45.104056",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Stock",
"name": "Batch",
- "owner": "harshada@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"create": 1,
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 60f6a68..19d0bec 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -130,12 +130,18 @@
if (this.frm.doc.docstatus===0) {
this.frm.add_custom_button(__('Sales Order'),
function() {
+ if (!me.frm.doc.customer) {
+ frappe.throw({
+ title: __("Mandatory"),
+ message: __("Please Select a Customer")
+ });
+ }
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
source_doctype: "Sales Order",
target: me.frm,
setters: {
- customer: me.frm.doc.customer || undefined,
+ customer: me.frm.doc.customer,
},
get_query_filters: {
docstatus: 1,
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 38e5fe5..faeeb57 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -26,19 +26,19 @@
refresh: function(frm) {
if (frm.doc.is_stock_item) {
- frm.add_custom_button(__("Balance"), function() {
+ frm.add_custom_button(__("Stock Balance"), function() {
frappe.route_options = {
"item_code": frm.doc.name
}
frappe.set_route("query-report", "Stock Balance");
}, __("View"));
- frm.add_custom_button(__("Ledger"), function() {
+ frm.add_custom_button(__("Stock Ledger"), function() {
frappe.route_options = {
"item_code": frm.doc.name
}
frappe.set_route("query-report", "Stock Ledger");
}, __("View"));
- frm.add_custom_button(__("Projected"), function() {
+ frm.add_custom_button(__("Stock Projected Qty"), function() {
frappe.route_options = {
"item_code": frm.doc.name
}
diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json
index 90a392c..b24d621 100644
--- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json
+++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json
@@ -133,11 +133,11 @@
],
"idx": 1,
"istable": 1,
- "modified": "2019-11-12 15:41:21.053462",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Item",
- "owner": "wasim@webnotestech.com",
+ "owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
diff --git a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json
index f49c147..9b2b5da 100644
--- a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json
+++ b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json
@@ -173,11 +173,11 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-07-20 10:49:34.228751",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Purchase Receipt",
- "owner": "wasim@webnotestech.com",
+ "owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 50c18f6..c504e23 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -116,12 +116,18 @@
if (this.frm.doc.docstatus == 0) {
this.frm.add_custom_button(__('Purchase Order'),
function () {
+ if (!me.frm.doc.supplier) {
+ frappe.throw({
+ title: __("Mandatory"),
+ message: __("Please Select a Supplier")
+ });
+ }
erpnext.utils.map_current_doc({
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
source_doctype: "Purchase Order",
target: me.frm,
setters: {
- supplier: me.frm.doc.supplier || undefined,
+ supplier: me.frm.doc.supplier,
},
get_query_filters: {
docstatus: 1,
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 67161aa..1e7153e 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -494,8 +494,7 @@
"expected_value_after_useful_life": 10,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 3,
- "frequency_of_depreciation": 1,
- "depreciation_start_date": frappe.utils.nowdate()
+ "frequency_of_depreciation": 1
})
asset.submit()
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
index c951066..3643174 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
@@ -73,7 +73,8 @@
"fieldname": "reference_type",
"fieldtype": "Select",
"label": "Reference Type",
- "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry"
+ "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry",
+ "reqd": 1
},
{
"fieldname": "reference_name",
@@ -84,7 +85,8 @@
"label": "Reference Name",
"oldfieldname": "purchase_receipt_no",
"oldfieldtype": "Link",
- "options": "reference_type"
+ "options": "reference_type",
+ "reqd": 1
},
{
"fieldname": "section_break_7",
@@ -231,9 +233,10 @@
],
"icon": "fa fa-search",
"idx": 1,
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-26 17:50:25.068222",
+ "modified": "2020-09-12 16:11:31.910508",
"modified_by": "Administrator",
"module": "Stock",
"name": "Quality Inspection",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 9845bc2..39fd029 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -23,6 +23,24 @@
}
});
+ frm.set_query('source_warehouse_address', function() {
+ return {
+ filters: {
+ link_doctype: 'Warehouse',
+ link_name: frm.doc.from_warehouse
+ }
+ }
+ });
+
+ frm.set_query('target_warehouse_address', function() {
+ return {
+ filters: {
+ link_doctype: 'Warehouse',
+ link_name: frm.doc.to_warehouse
+ }
+ }
+ });
+
frappe.db.get_value('Stock Settings', {name: 'Stock Settings'}, 'sample_retention_warehouse', (r) => {
if (r.sample_retention_warehouse) {
var filters = [
@@ -138,6 +156,7 @@
mr_item.item_code = item.item_code;
mr_item.item_name = item.item_name;
mr_item.uom = item.uom;
+ mr_item.stock_uom = item.stock_uom;
mr_item.conversion_factor = item.conversion_factor;
mr_item.item_group = item.item_group;
mr_item.description = item.description;
diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.js b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.js
index 23700c9..2499c80 100644
--- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.js
+++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.js
@@ -9,13 +9,15 @@
"fieldtype": "Date",
"width": "80",
"default": frappe.sys_defaults.year_start_date,
+ "reqd": 1
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"width": "80",
- "default": frappe.datetime.get_today()
+ "default": frappe.datetime.get_today(),
+ "reqd": 1
},
{
"fieldname": "item",
diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
index 2c95084..8f3e246 100644
--- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
+++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
@@ -11,6 +11,9 @@
def execute(filters=None):
if not filters: filters = {}
+ if filters.from_date > filters.to_date:
+ frappe.throw(_("From Date must be before To Date"))
+
float_precision = cint(frappe.db.get_default("float_precision")) or 3
columns = get_columns(filters)
diff --git a/erpnext/support/report/minutes_to_first_response_for_issues/__init__.py b/erpnext/stock/report/stock_qty_vs_serial_no_count/__init__.py
similarity index 100%
copy from erpnext/support/report/minutes_to_first_response_for_issues/__init__.py
copy to erpnext/stock/report/stock_qty_vs_serial_no_count/__init__.py
diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js
new file mode 100644
index 0000000..2a0fd40
--- /dev/null
+++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js
@@ -0,0 +1,42 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Stock Qty vs Serial No Count"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"warehouse",
+ "label": __("Warehouse"),
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "get_query": function() {
+ const company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: { 'company': company }
+ }
+ },
+ "reqd": 1
+ },
+ ],
+
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+ if (column.fieldname == "difference" && data) {
+ if (data.difference > 0) {
+ value = "<span style='color:red'>" + value + "</span>";
+ }
+ else if (data.difference < 0) {
+ value = "<span style='color:red'>" + value + "</span>";
+ }
+ }
+ return value;
+ }
+};
diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.json b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.json
new file mode 100644
index 0000000..c7108b5
--- /dev/null
+++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.json
@@ -0,0 +1,27 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-07-23 19:31:32.395011",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-07-23 19:32:02.168185",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock Qty vs Serial No Count",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Item",
+ "report_name": "Stock Qty vs Serial No Count",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Stock Manager"
+ },
+ {
+ "role": "Stock User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py
new file mode 100644
index 0000000..55f041c
--- /dev/null
+++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py
@@ -0,0 +1,80 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ columns = get_columns()
+ data = get_data(filters.warehouse)
+ return columns, data
+
+def get_columns():
+ columns = [
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 200
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 200
+ },
+ {
+ "label": _("Serial No Count"),
+ "fieldname": "total",
+ "fieldtype": "Float",
+ "width": 150
+ },
+ {
+ "label": _("Stock Qty"),
+ "fieldname": "stock_qty",
+ "fieldtype": "Float",
+ "width": 150
+ },
+ {
+ "label": _("Difference"),
+ "fieldname": "difference",
+ "fieldtype": "Float",
+ "width": 150
+ },
+ ]
+
+ return columns
+
+def get_data(warehouse):
+ serial_item_list = frappe.get_all("Item", filters={
+ 'has_serial_no': True,
+ }, fields=['item_code', 'item_name'])
+
+ status_list = ['Active', 'Expired']
+ data = []
+ for item in serial_item_list:
+ total_serial_no = frappe.db.count("Serial No",
+ filters={"item_code": item.item_code, "status": ("in", status_list), "warehouse": warehouse})
+
+ actual_qty = frappe.db.get_value('Bin', fieldname=['actual_qty'],
+ filters={"warehouse": warehouse, "item_code": item.item_code})
+
+ # frappe.db.get_value returns null if no record exist.
+ if not actual_qty:
+ actual_qty = 0
+
+ difference = total_serial_no - actual_qty
+
+ row = {
+ "item_code": item.item_code,
+ "item_name": item.item_name,
+ "total": total_serial_no,
+ "stock_qty": actual_qty,
+ "difference": difference,
+ }
+
+ data.append(row)
+
+ return data
\ No newline at end of file
diff --git a/erpnext/support/desk_page/support/support.json b/erpnext/support/desk_page/support/support.json
index b1ad7c8..28410f3 100644
--- a/erpnext/support/desk_page/support/support.json
+++ b/erpnext/support/desk_page/support/support.json
@@ -28,7 +28,7 @@
{
"hidden": 0,
"label": "Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Issue\"\n ],\n \"doctype\": \"Issue\",\n \"is_query_report\": true,\n \"label\": \"Minutes to First Response for Issues\",\n \"name\": \"Minutes to First Response for Issues\",\n \"type\": \"report\"\n }\n]"
+ "links": "[\n {\n \"dependencies\": [\n \"Issue\"\n ],\n \"doctype\": \"Issue\",\n \"is_query_report\": true,\n \"label\": \"First Response Time for Issues\",\n \"name\": \"First Response Time for Issues\",\n \"type\": \"report\"\n }\n]"
}
],
"category": "Modules",
@@ -43,7 +43,7 @@
"idx": 0,
"is_standard": 1,
"label": "Support",
- "modified": "2020-06-04 11:54:56.124219",
+ "modified": "2020-08-11 15:49:34.307341",
"modified_by": "Administrator",
"module": "Support",
"name": "Support",
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index 858564a..fe01d4b 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -2,10 +2,14 @@
onload: function(frm) {
frm.email_field = "raised_by";
- frappe.db.get_value("Support Settings", {name: "Support Settings"}, "allow_resetting_service_level_agreement", (r) => {
- if (!r.allow_resetting_service_level_agreement) {
- frm.set_df_property("reset_service_level_agreement", "hidden", 1) ;
- }
+ frappe.db.get_value("Support Settings", {name: "Support Settings"},
+ ["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => {
+ if (r && r.track_service_level_agreement == "0") {
+ frm.set_df_property("service_level_section", "hidden", 1);
+ }
+ if (r && r.allow_resetting_service_level_agreement == "0") {
+ frm.set_df_property("reset_service_level_agreement", "hidden", 1);
+ }
});
if (frm.doc.service_level_agreement) {
@@ -38,7 +42,7 @@
},
refresh: function (frm) {
- if (frm.doc.status !== "Closed" && frm.doc.agreement_fulfilled === "Ongoing") {
+ if (frm.doc.status !== "Closed" && frm.doc.agreement_status === "Ongoing") {
if (frm.doc.service_level_agreement) {
frappe.call({
'method': 'frappe.client.get',
@@ -85,14 +89,14 @@
if (frm.doc.service_level_agreement) {
frm.dashboard.clear_headline();
- let agreement_fulfilled = (frm.doc.agreement_fulfilled == "Fulfilled") ?
+ let agreement_status = (frm.doc.agreement_status == "Fulfilled") ?
{"indicator": "green", "msg": "Service Level Agreement has been fulfilled"} :
{"indicator": "red", "msg": "Service Level Agreement Failed"};
frm.dashboard.set_headline_alert(
'<div class="row">' +
'<div class="col-xs-12">' +
- '<span class="indicator whitespace-nowrap '+ agreement_fulfilled.indicator +'"><span class="hidden-xs">'+ agreement_fulfilled.msg +'</span></span> ' +
+ '<span class="indicator whitespace-nowrap '+ agreement_status.indicator +'"><span class="hidden-xs">'+ agreement_status.msg +'</span></span> ' +
'</div>' +
'</div>'
);
@@ -198,13 +202,13 @@
frm.dashboard.clear_headline();
var time_to_respond = get_status(frm.doc.response_by_variance);
- if (!frm.doc.first_responded_on && frm.doc.agreement_fulfilled === "Ongoing") {
- time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_fulfilled);
+ if (!frm.doc.first_responded_on && frm.doc.agreement_status === "Ongoing") {
+ time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_status);
}
var time_to_resolve = get_status(frm.doc.resolution_by_variance);
- if (!frm.doc.resolution_date && frm.doc.agreement_fulfilled === "Ongoing") {
- time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_fulfilled);
+ if (!frm.doc.resolution_date && frm.doc.agreement_status === "Ongoing") {
+ time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_status);
}
frm.dashboard.set_headline_alert(
@@ -219,10 +223,10 @@
);
}
-function get_time_left(timestamp, agreement_fulfilled) {
+function get_time_left(timestamp, agreement_status) {
const diff = moment(timestamp).diff(moment());
const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : "Failed";
- let indicator = (diff_display == 'Failed' && agreement_fulfilled != "Fulfilled") ? "red" : "green";
+ let indicator = (diff_display == 'Failed' && agreement_status != "Fulfilled") ? "red" : "green";
return {"diff_display": diff_display, "indicator": indicator};
}
diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json
index 6525ab2..a43381c 100644
--- a/erpnext/support/doctype/issue/issue.json
+++ b/erpnext/support/doctype/issue/issue.json
@@ -27,17 +27,25 @@
"response_by_variance",
"reset_service_level_agreement",
"cb",
- "agreement_fulfilled",
+ "agreement_status",
"resolution_by",
"resolution_by_variance",
"service_level_agreement_creation",
"on_hold_since",
"total_hold_time",
"response",
- "mins_to_first_response",
+ "first_response_time",
"first_responded_on",
"column_break_26",
"avg_response_time",
+ "section_break_19",
+ "resolution_details",
+ "column_break1",
+ "opening_date",
+ "opening_time",
+ "resolution_date",
+ "resolution_time",
+ "user_resolution_time",
"additional_info",
"lead",
"contact",
@@ -46,23 +54,14 @@
"customer_name",
"project",
"company",
- "section_break_19",
- "resolution_details",
- "column_break1",
- "opening_date",
- "opening_time",
- "resolution_date",
- "content_type",
- "attachment",
"via_customer_portal",
- "resolution_time",
- "user_resolution_time"
+ "attachment",
+ "content_type"
],
"fields": [
{
"fieldname": "subject_section",
"fieldtype": "Section Break",
- "label": "Subject",
"options": "fa fa-flag"
},
{
@@ -158,7 +157,7 @@
"collapsible": 1,
"fieldname": "service_level_section",
"fieldtype": "Section Break",
- "label": "Service Level"
+ "label": "Service Level Agreement Details"
},
{
"fieldname": "service_level_agreement",
@@ -191,14 +190,7 @@
"collapsible": 1,
"fieldname": "response",
"fieldtype": "Section Break",
- "label": "Response"
- },
- {
- "bold": 1,
- "fieldname": "mins_to_first_response",
- "fieldtype": "Float",
- "label": "Mins to First Response",
- "read_only": 1
+ "label": "Response Details"
},
{
"fieldname": "first_responded_on",
@@ -261,7 +253,7 @@
"collapsible": 1,
"fieldname": "section_break_19",
"fieldtype": "Section Break",
- "label": "Resolution"
+ "label": "Resolution Details"
},
{
"depends_on": "eval:!doc.__islocal",
@@ -327,27 +319,18 @@
"label": "Via Customer Portal"
},
{
- "default": "Ongoing",
- "depends_on": "eval: doc.service_level_agreement",
- "fieldname": "agreement_fulfilled",
- "fieldtype": "Select",
- "label": "Service Level Agreement Fulfilled",
- "options": "Ongoing\nFulfilled\nFailed",
- "read_only": 1
- },
- {
"depends_on": "eval: doc.service_level_agreement && doc.status != 'Replied';",
- "description": "in hours",
"fieldname": "response_by_variance",
- "fieldtype": "Float",
+ "fieldtype": "Duration",
+ "hide_seconds": 1,
"label": "Response By Variance",
"read_only": 1
},
{
"depends_on": "eval: doc.service_level_agreement && doc.status != 'Replied';",
- "description": "in hours",
"fieldname": "resolution_by_variance",
- "fieldtype": "Float",
+ "fieldtype": "Duration",
+ "hide_seconds": 1,
"label": "Resolution By Variance",
"read_only": 1
},
@@ -406,12 +389,28 @@
"fieldtype": "Duration",
"label": "Total Hold Time",
"read_only": 1
+ },
+ {
+ "default": "Ongoing",
+ "depends_on": "eval: doc.service_level_agreement",
+ "fieldname": "agreement_status",
+ "fieldtype": "Select",
+ "label": "Service Level Agreement Status",
+ "options": "Ongoing\nFulfilled\nFailed",
+ "read_only": 1
+ },
+ {
+ "bold": 1,
+ "fieldname": "first_response_time",
+ "fieldtype": "Duration",
+ "label": "First Response Time",
+ "read_only": 1
}
],
"icon": "fa fa-ticket",
"idx": 7,
"links": [],
- "modified": "2020-06-10 12:47:37.146914",
+ "modified": "2020-08-11 18:49:07.574769",
"modified_by": "Administrator",
"module": "Support",
"name": "Issue",
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 87168e1..920c13c 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -61,7 +61,7 @@
if self.status in ["Closed", "Resolved"] and status not in ["Resolved", "Closed"]:
self.resolution_date = frappe.flags.current_time or now_datetime()
- if frappe.db.get_value("Issue", self.name, "agreement_fulfilled") == "Ongoing":
+ if frappe.db.get_value("Issue", self.name, "agreement_status") == "Ongoing":
set_service_level_agreement_variance(issue=self.name)
self.update_agreement_status()
set_resolution_time(issue=self)
@@ -72,7 +72,7 @@
self.resolution_date = None
self.reset_issue_metrics()
# enable SLA and variance on Reopen
- self.agreement_fulfilled = "Ongoing"
+ self.agreement_status = "Ongoing"
set_service_level_agreement_variance(issue=self.name)
self.handle_hold_time(status)
@@ -113,39 +113,39 @@
if not self.first_responded_on:
response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
response_by = add_to_date(response_by, seconds=round(last_hold_time))
- response_by_variance = round(time_diff_in_hours(response_by, now_time))
+ response_by_variance = round(time_diff_in_seconds(response_by, now_time))
update_values['response_by'] = response_by
- update_values['response_by_variance'] = response_by_variance + (last_hold_time // 3600)
+ update_values['response_by_variance'] = response_by_variance + last_hold_time
resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
resolution_by = add_to_date(resolution_by, seconds=round(last_hold_time))
- resolution_by_variance = round(time_diff_in_hours(resolution_by, now_time))
+ resolution_by_variance = round(time_diff_in_seconds(resolution_by, now_time))
update_values['resolution_by'] = resolution_by
- update_values['resolution_by_variance'] = resolution_by_variance + (last_hold_time // 3600)
+ update_values['resolution_by_variance'] = resolution_by_variance + last_hold_time
update_values['on_hold_since'] = None
self.db_set(update_values)
def update_agreement_status(self):
- if self.service_level_agreement and self.agreement_fulfilled == "Ongoing":
+ if self.service_level_agreement and self.agreement_status == "Ongoing":
if frappe.db.get_value("Issue", self.name, "response_by_variance") < 0 or \
frappe.db.get_value("Issue", self.name, "resolution_by_variance") < 0:
- self.agreement_fulfilled = "Failed"
+ self.agreement_status = "Failed"
else:
- self.agreement_fulfilled = "Fulfilled"
+ self.agreement_status = "Fulfilled"
- def update_agreement_fulfilled_on_custom_status(self):
+ def update_agreement_status_on_custom_status(self):
"""
Update Agreement Fulfilled status using Custom Scripts for Custom Issue Status
"""
if not self.first_responded_on: # first_responded_on set when first reply is sent to customer
- self.response_by_variance = round(time_diff_in_hours(self.response_by, now_datetime()), 2)
+ self.response_by_variance = round(time_diff_in_seconds(self.response_by, now_datetime()), 2)
if not self.resolution_date: # resolution_date set when issue has been closed
- self.resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_datetime()), 2)
+ self.resolution_by_variance = round(time_diff_in_seconds(self.resolution_by, now_datetime()), 2)
- self.agreement_fulfilled = "Fulfilled" if self.response_by_variance > 0 and self.resolution_by_variance > 0 else "Failed"
+ self.agreement_status = "Fulfilled" if self.response_by_variance > 0 and self.resolution_by_variance > 0 else "Failed"
def create_communication(self):
communication = frappe.new_doc("Communication")
@@ -172,7 +172,7 @@
replicated_issue = deepcopy(self)
replicated_issue.subject = subject
replicated_issue.issue_split_from = self.name
- replicated_issue.mins_to_first_response = 0
+ replicated_issue.first_response_time = 0
replicated_issue.first_responded_on = None
replicated_issue.creation = now_datetime()
@@ -180,7 +180,7 @@
if replicated_issue.service_level_agreement:
replicated_issue.service_level_agreement_creation = now_datetime()
replicated_issue.service_level_agreement = None
- replicated_issue.agreement_fulfilled = "Ongoing"
+ replicated_issue.agreement_status = "Ongoing"
replicated_issue.response_by = None
replicated_issue.response_by_variance = None
replicated_issue.resolution_by = None
@@ -241,8 +241,8 @@
self.response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
self.resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
- self.response_by_variance = round(time_diff_in_hours(self.response_by, now_datetime()))
- self.resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_datetime()))
+ self.response_by_variance = round(time_diff_in_seconds(self.response_by, now_datetime()))
+ self.resolution_by_variance = round(time_diff_in_seconds(self.resolution_by, now_datetime()))
def change_service_level_agreement_and_priority(self):
if self.service_level_agreement and frappe.db.exists("Issue", self.name) and \
@@ -271,7 +271,7 @@
self.service_level_agreement_creation = now_datetime()
self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
- self.agreement_fulfilled = "Ongoing"
+ self.agreement_status = "Ongoing"
self.save()
def reset_issue_metrics(self):
@@ -347,7 +347,7 @@
def set_service_level_agreement_variance(issue=None):
current_time = frappe.flags.current_time or now_datetime()
- filters = {"status": "Open", "agreement_fulfilled": "Ongoing"}
+ filters = {"status": "Open", "agreement_status": "Ongoing"}
if issue:
filters = {"name": issue}
@@ -358,13 +358,13 @@
variance = round(time_diff_in_hours(doc.response_by, current_time), 2)
frappe.db.set_value(dt="Issue", dn=doc.name, field="response_by_variance", val=variance, update_modified=False)
if variance < 0:
- frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_fulfilled", val="Failed", update_modified=False)
+ frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_status", val="Failed", update_modified=False)
if not doc.resolution_date: # resolution_date set when issue has been closed
variance = round(time_diff_in_hours(doc.resolution_by, current_time), 2)
frappe.db.set_value(dt="Issue", dn=doc.name, field="resolution_by_variance", val=variance, update_modified=False)
if variance < 0:
- frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_fulfilled", val="Failed", update_modified=False)
+ frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_status", val="Failed", update_modified=False)
def set_resolution_time(issue):
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index fb8ceb5..c962dc6 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -73,7 +73,7 @@
issue.status = 'Closed'
issue.save()
- self.assertEqual(issue.agreement_fulfilled, 'Fulfilled')
+ self.assertEqual(issue.agreement_status, 'Fulfilled')
def test_issue_metrics(self):
creation = datetime.datetime(2020, 3, 4, 4, 0)
diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.json b/erpnext/support/doctype/warranty_claim/warranty_claim.json
index ae1a7a5..88ee4a3 100644
--- a/erpnext/support/doctype/warranty_claim/warranty_claim.json
+++ b/erpnext/support/doctype/warranty_claim/warranty_claim.json
@@ -361,11 +361,11 @@
],
"icon": "fa fa-bug",
"idx": 1,
- "modified": "2019-05-24 10:56:30.626200",
+ "modified": "2020-09-18 17:26:09.703215",
"modified_by": "Administrator",
"module": "Support",
"name": "Warranty Claim",
- "owner": "harshada@webnotestech.com",
+ "owner": "Administrator",
"permissions": [
{
"create": 1,
diff --git a/erpnext/support/report/minutes_to_first_response_for_issues/__init__.py b/erpnext/support/report/first_response_time_for_issues/__init__.py
similarity index 100%
rename from erpnext/support/report/minutes_to_first_response_for_issues/__init__.py
rename to erpnext/support/report/first_response_time_for_issues/__init__.py
diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
new file mode 100644
index 0000000..576e0b7
--- /dev/null
+++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.js
@@ -0,0 +1,44 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["First Response Time for Issues"] = {
+ "filters": [
+ {
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.add_days(frappe.datetime.nowdate(), -30)
+ },
+ {
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default":frappe.datetime.nowdate()
+ }
+ ],
+ get_chart_data: function(_columns, result) {
+ return {
+ data: {
+ labels: result.map(d => d[0]),
+ datasets: [{
+ name: 'First Response Time',
+ values: result.map(d => d[1])
+ }]
+ },
+ type: "line",
+ tooltipOptions: {
+ formatTooltipY: d => {
+ let duration_options = {
+ hide_days: 0,
+ hide_seconds: 0
+ };
+ value = frappe.utils.get_formatted_duration(d, duration_options);
+ return value;
+ }
+ }
+ }
+ }
+};
diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.json b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.json
new file mode 100644
index 0000000..c4fe6f5
--- /dev/null
+++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.json
@@ -0,0 +1,26 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-08-10 18:12:42.391224",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Test 2",
+ "modified": "2020-08-10 18:12:42.391224",
+ "modified_by": "Administrator",
+ "module": "Support",
+ "name": "First Response Time for Issues",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "select date(creation) as creation_date, avg(mins_to_first_response) from tabIssue where creation > '2016-05-01' group by date(creation) order by creation_date;",
+ "ref_doctype": "Issue",
+ "report_name": "First Response Time for Issues",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Support Team"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py
new file mode 100644
index 0000000..922da2b
--- /dev/null
+++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute(filters=None):
+ columns = [
+ {
+ 'fieldname': 'creation_date',
+ 'label': 'Date',
+ 'fieldtype': 'Date',
+ 'width': 300
+ },
+ {
+ 'fieldname': 'first_response_time',
+ 'fieldtype': 'Duration',
+ 'label': 'First Response Time',
+ 'width': 300
+ },
+ ]
+
+ data = frappe.db.sql('''
+ SELECT
+ date(creation) as creation_date,
+ avg(first_response_time) as avg_response_time
+ FROM tabIssue
+ WHERE
+ date(creation) between %s and %s
+ and first_response_time > 0
+ GROUP BY creation_date
+ ORDER BY creation_date desc
+ ''', (filters.from_date, filters.to_date))
+
+ return columns, data
\ No newline at end of file
diff --git a/erpnext/support/report/minutes_to_first_response_for_issues/minutes_to_first_response_for_issues.js b/erpnext/support/report/minutes_to_first_response_for_issues/minutes_to_first_response_for_issues.js
deleted file mode 100644
index 034e777..0000000
--- a/erpnext/support/report/minutes_to_first_response_for_issues/minutes_to_first_response_for_issues.js
+++ /dev/null
@@ -1,30 +0,0 @@
-frappe.query_reports["Minutes to First Response for Issues"] = {
- "filters": [
- {
- "fieldname":"from_date",
- "label": __("From Date"),
- "fieldtype": "Date",
- 'reqd': 1,
- "default": frappe.datetime.add_days(frappe.datetime.nowdate(), -30)
- },
- {
- "fieldname":"to_date",
- "label": __("To Date"),
- "fieldtype": "Date",
- 'reqd': 1,
- "default":frappe.datetime.nowdate()
- },
- ],
- get_chart_data: function(columns, result) {
- return {
- data: {
- labels: result.map(d => d[0]),
- datasets: [{
- name: 'Mins to first response',
- values: result.map(d => d[1])
- }]
- },
- type: 'line',
- }
- }
-}
diff --git a/erpnext/support/report/minutes_to_first_response_for_issues/minutes_to_first_response_for_issues.json b/erpnext/support/report/minutes_to_first_response_for_issues/minutes_to_first_response_for_issues.json
deleted file mode 100644
index 539d3d9..0000000
--- a/erpnext/support/report/minutes_to_first_response_for_issues/minutes_to_first_response_for_issues.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2016-06-14 17:44:26.034112",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 2,
- "is_standard": "Yes",
- "modified": "2017-02-24 20:06:18.391100",
- "modified_by": "Administrator",
- "module": "Support",
- "name": "Minutes to First Response for Issues",
- "owner": "Administrator",
- "query": "select date(creation) as creation_date, avg(mins_to_first_response) from tabIssue where creation > '2016-05-01' group by date(creation) order by creation_date;",
- "ref_doctype": "Issue",
- "report_name": "Minutes to First Response for Issues",
- "report_type": "Script Report",
- "roles": [
- {
- "role": "Support Team"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/support/report/minutes_to_first_response_for_issues/minutes_to_first_response_for_issues.py b/erpnext/support/report/minutes_to_first_response_for_issues/minutes_to_first_response_for_issues.py
deleted file mode 100644
index 57c2d44..0000000
--- a/erpnext/support/report/minutes_to_first_response_for_issues/minutes_to_first_response_for_issues.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-
-def execute(filters=None):
- columns = [
- {
- 'fieldname': 'creation_date',
- 'label': 'Date',
- 'fieldtype': 'Date'
- },
- {
- 'fieldname': 'mins',
- 'fieldtype': 'Float',
- 'label': 'Mins to First Response'
- },
- ]
-
- data = frappe.db.sql('''select date(creation) as creation_date,
- avg(mins_to_first_response) as mins
- from tabIssue
- where date(creation) between %s and %s
- and mins_to_first_response > 0
- group by creation_date order by creation_date desc''', (filters.from_date, filters.to_date))
-
- return columns, data
diff --git a/erpnext/utilities/activation.py b/erpnext/utilities/activation.py
index 63c36b3..7b17c8c 100644
--- a/erpnext/utilities/activation.py
+++ b/erpnext/utilities/activation.py
@@ -11,8 +11,39 @@
activation_level = 0
sales_data = []
min_count = 0
- doctypes = {"Item": 5, "Customer": 5, "Sales Order": 2, "Sales Invoice": 2, "Purchase Order": 2, "Employee": 3, "Lead": 3, "Quotation": 3,
- "Payment Entry": 2, "User": 5, "Student": 5, "Instructor": 5, "BOM": 3, "Journal Entry": 3, "Stock Entry": 3}
+ doctypes = {
+ "Asset": 5,
+ "BOM": 3,
+ "Customer": 5,
+ "Delivery Note": 5,
+ "Employee": 3,
+ "Instructor": 5,
+ "Instructor": 5,
+ "Issue": 5,
+ "Item": 5,
+ "Journal Entry": 3,
+ "Lead": 3,
+ "Leave Application": 5,
+ "Material Request": 5,
+ "Opportunity": 5,
+ "Payment Entry": 2,
+ "Project": 5,
+ "Purchase Order": 2,
+ "Purchase Invoice": 5,
+ "Purchase Receipt": 5,
+ "Quotation": 3,
+ "Salary Slip": 5,
+ "Salary Structure": 5,
+ "Sales Order": 2,
+ "Sales Invoice": 2,
+ "Stock Entry": 3,
+ "Student": 5,
+ "Supplier": 5,
+ "Task": 5,
+ "User": 5,
+ "Work Order": 5
+ }
+
for doctype, min_count in iteritems(doctypes):
count = frappe.db.count(doctype)
if count > min_count:
diff --git a/erpnext/utilities/desk_page/utilities/utilities.json b/erpnext/utilities/desk_page/utilities/utilities.json
new file mode 100644
index 0000000..591eab5
--- /dev/null
+++ b/erpnext/utilities/desk_page/utilities/utilities.json
@@ -0,0 +1,29 @@
+{
+ "cards": [
+ {
+ "hidden": 0,
+ "label": "Video",
+ "links": "[\n {\n \"description\": \"Video\",\n \"label\": \"Video\",\n \"name\": \"Video\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Video settings\",\n \"label\": \"Video Settings\",\n \"name\": \"Video Settings\",\n \"type\": \"doctype\"\n }\n]"
+ }
+ ],
+ "category": "Modules",
+ "charts": [],
+ "creation": "2020-09-10 12:21:22.335307",
+ "developer_mode_only": 0,
+ "disable_user_customization": 0,
+ "docstatus": 0,
+ "doctype": "Desk Page",
+ "extends_another_page": 0,
+ "hide_custom": 0,
+ "idx": 0,
+ "is_standard": 1,
+ "label": "Utilities",
+ "modified": "2020-09-10 12:33:30.089853",
+ "modified_by": "user@erpnext.com",
+ "module": "Utilities",
+ "name": "Utilities",
+ "owner": "user@erpnext.com",
+ "pin_to_bottom": 1,
+ "pin_to_top": 0,
+ "shortcuts": []
+}
\ No newline at end of file
diff --git a/erpnext/utilities/doctype/video/video.js b/erpnext/utilities/doctype/video/video.js
index 056bd3c..9cb5a15 100644
--- a/erpnext/utilities/doctype/video/video.js
+++ b/erpnext/utilities/doctype/video/video.js
@@ -2,7 +2,16 @@
// For license information, please see license.txt
frappe.ui.form.on('Video', {
- // refresh: function(frm) {
+ refresh: function (frm) {
+ frm.events.toggle_youtube_statistics_section(frm);
+ frm.add_custom_button("Watch Video", () => frappe.help.show_video(frm.doc.url, frm.doc.title));
+ },
- // }
+ toggle_youtube_statistics_section: (frm) => {
+ if (frm.doc.provider === "YouTube") {
+ frappe.db.get_single_value("Video Settings", "enable_youtube_tracking").then( val => {
+ frm.toggle_display("youtube_tracking_section", val);
+ });
+ }
+ }
});
diff --git a/erpnext/utilities/doctype/video/video.json b/erpnext/utilities/doctype/video/video.json
index 5d2cc13..2a82db2 100644
--- a/erpnext/utilities/doctype/video/video.json
+++ b/erpnext/utilities/doctype/video/video.json
@@ -11,11 +11,19 @@
"title",
"provider",
"url",
+ "youtube_video_id",
"column_break_4",
"publish_date",
"duration",
+ "youtube_tracking_section",
+ "like_count",
+ "view_count",
+ "col_break",
+ "dislike_count",
+ "comment_count",
"section_break_7",
- "description"
+ "description",
+ "image"
],
"fields": [
{
@@ -37,7 +45,6 @@
{
"fieldname": "url",
"fieldtype": "Data",
- "in_list_view": 1,
"label": "URL",
"reqd": 1
},
@@ -48,11 +55,12 @@
{
"fieldname": "publish_date",
"fieldtype": "Date",
+ "in_list_view": 1,
"label": "Publish Date"
},
{
"fieldname": "duration",
- "fieldtype": "Data",
+ "fieldtype": "Duration",
"label": "Duration"
},
{
@@ -62,13 +70,67 @@
{
"fieldname": "description",
"fieldtype": "Text Editor",
- "in_list_view": 1,
"label": "Description",
"reqd": 1
+ },
+ {
+ "fieldname": "like_count",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Likes",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "view_count",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Views",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "dislike_count",
+ "fieldtype": "Float",
+ "label": "Dislikes",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "comment_count",
+ "fieldtype": "Float",
+ "label": "Comments",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "hidden": 1,
+ "label": "Image",
+ "no_copy": 1
+ },
+ {
+ "depends_on": "eval:doc.provider==\"YouTube\"",
+ "fieldname": "youtube_tracking_section",
+ "fieldtype": "Section Break",
+ "label": "Youtube Statistics"
+ },
+ {
+ "fieldname": "youtube_video_id",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Youtube ID",
+ "read_only": 1
}
],
+ "image_field": "image",
"links": [],
- "modified": "2020-07-21 19:29:46.603734",
+ "modified": "2020-09-07 17:02:20.185794",
"modified_by": "Administrator",
"module": "Utilities",
"name": "Video",
diff --git a/erpnext/utilities/doctype/video/video.py b/erpnext/utilities/doctype/video/video.py
index 3c17b56..c2e414e 100644
--- a/erpnext/utilities/doctype/video/video.py
+++ b/erpnext/utilities/doctype/video/video.py
@@ -3,8 +3,144 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-# import frappe
+import frappe
+import re
+import pytz
from frappe.model.document import Document
+from frappe import _
+from datetime import datetime
+from six import string_types
+from pyyoutube import Api
class Video(Document):
- pass
+ def validate(self):
+ if self.provider == "YouTube" and is_tracking_enabled():
+ self.set_video_id()
+ self.set_youtube_statistics()
+
+ def set_video_id(self):
+ if self.url and not self.get("youtube_video_id"):
+ self.youtube_video_id = get_id_from_url(self.url)
+
+ def set_youtube_statistics(self):
+ api_key = frappe.db.get_single_value("Video Settings", "api_key")
+ api = Api(api_key=api_key)
+
+ try:
+ video = api.get_video_by_id(video_id=self.youtube_video_id)
+ video_stats = video.items[0].to_dict().get('statistics')
+
+ self.like_count = video_stats.get('likeCount')
+ self.view_count = video_stats.get('viewCount')
+ self.dislike_count = video_stats.get('dislikeCount')
+ self.comment_count = video_stats.get('commentCount')
+
+ except Exception:
+ title = "Failed to Update YouTube Statistics for Video: {0}".format(self.name)
+ frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
+
+def is_tracking_enabled():
+ return frappe.db.get_single_value("Video Settings", "enable_youtube_tracking")
+
+
+def get_frequency(value):
+ # Return numeric value from frequency field, return 1 as fallback default value: 1 hour
+ if value != "Daily":
+ return frappe.utils.cint(value[:2].strip())
+ elif value:
+ return 24
+ return 1
+
+
+def update_youtube_data():
+ # Called every 30 minutes via hooks
+ enable_youtube_tracking, frequency = frappe.db.get_value("Video Settings", "Video Settings", ["enable_youtube_tracking", "frequency"])
+
+ if not enable_youtube_tracking:
+ return
+
+ frequency = get_frequency(frequency)
+ time = datetime.now()
+ timezone = pytz.timezone(frappe.utils.get_time_zone())
+ site_time = time.astimezone(timezone)
+
+ if frequency == 30:
+ batch_update_youtube_data()
+ elif site_time.hour % frequency == 0 and site_time.minute < 15:
+ # make sure it runs within the first 15 mins of the hour
+ batch_update_youtube_data()
+
+
+def get_formatted_ids(video_list):
+ # format ids to comma separated string for bulk request
+ ids = []
+ for video in video_list:
+ ids.append(video.youtube_video_id)
+
+ return ','.join(ids)
+
+
+@frappe.whitelist()
+def get_id_from_url(url):
+ """
+ Returns video id from url
+ :param youtube url: String URL
+ """
+ if not isinstance(url, string_types):
+ frappe.throw(_("URL can only be a string"), title=_("Invalid URL"))
+
+ pattern = re.compile(r'[a-z\:\//\.]+(youtube|youtu)\.(com|be)/(watch\?v=|embed/|.+\?v=)?([^"&?\s]{11})?')
+ id = pattern.match(url)
+ return id.groups()[-1]
+
+
+@frappe.whitelist()
+def batch_update_youtube_data():
+ def get_youtube_statistics(video_ids):
+ api_key = frappe.db.get_single_value("Video Settings", "api_key")
+ api = Api(api_key=api_key)
+ try:
+ video = api.get_video_by_id(video_id=video_ids)
+ video_stats = video.items
+ return video_stats
+ except Exception:
+ title = "Failed to Update YouTube Statistics"
+ frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
+
+ def prepare_and_set_data(video_list):
+ video_ids = get_formatted_ids(video_list)
+ stats = get_youtube_statistics(video_ids)
+ set_youtube_data(stats)
+
+ def set_youtube_data(entries):
+ for entry in entries:
+ video_stats = entry.to_dict().get('statistics')
+ video_id = entry.to_dict().get('id')
+ stats = {
+ 'like_count' : video_stats.get('likeCount'),
+ 'view_count' : video_stats.get('viewCount'),
+ 'dislike_count' : video_stats.get('dislikeCount'),
+ 'comment_count' : video_stats.get('commentCount'),
+ 'video_id': video_id
+ }
+
+ frappe.db.sql("""
+ UPDATE `tabVideo`
+ SET
+ like_count = %(like_count)s,
+ view_count = %(view_count)s,
+ dislike_count = %(dislike_count)s,
+ comment_count = %(comment_count)s
+ WHERE youtube_video_id = %(video_id)s""", stats)
+
+ video_list = frappe.get_all("Video", fields=["youtube_video_id"])
+ if len(video_list) > 50:
+ # Update in batches of 50
+ start, end = 0, 50
+ while start < len(video_list):
+ batch = video_list[start:end]
+ prepare_and_set_data(batch)
+ start += 50
+ end += 50
+ else:
+ prepare_and_set_data(video_list)
diff --git a/erpnext/utilities/doctype/video/video_list.js b/erpnext/utilities/doctype/video/video_list.js
new file mode 100644
index 0000000..8273a4a
--- /dev/null
+++ b/erpnext/utilities/doctype/video/video_list.js
@@ -0,0 +1,7 @@
+frappe.listview_settings["Video"] = {
+ onload: (listview) => {
+ listview.page.add_menu_item(__("Video Settings"), function() {
+ frappe.set_route("Form","Video Settings", "Video Settings");
+ });
+ }
+}
\ No newline at end of file
diff --git a/erpnext/support/report/minutes_to_first_response_for_issues/__init__.py b/erpnext/utilities/doctype/video_settings/__init__.py
similarity index 100%
copy from erpnext/support/report/minutes_to_first_response_for_issues/__init__.py
copy to erpnext/utilities/doctype/video_settings/__init__.py
diff --git a/erpnext/utilities/doctype/video_settings/test_video_settings.py b/erpnext/utilities/doctype/video_settings/test_video_settings.py
new file mode 100644
index 0000000..b217afe
--- /dev/null
+++ b/erpnext/utilities/doctype/video_settings/test_video_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestVideoSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/utilities/doctype/video_settings/video_settings.js b/erpnext/utilities/doctype/video_settings/video_settings.js
new file mode 100644
index 0000000..9ac8b9e
--- /dev/null
+++ b/erpnext/utilities/doctype/video_settings/video_settings.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Video Settings', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/utilities/doctype/video_settings/video_settings.json b/erpnext/utilities/doctype/video_settings/video_settings.json
new file mode 100644
index 0000000..fb3274d
--- /dev/null
+++ b/erpnext/utilities/doctype/video_settings/video_settings.json
@@ -0,0 +1,60 @@
+{
+ "actions": [],
+ "creation": "2020-08-02 03:50:21.339609",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "enable_youtube_tracking",
+ "api_key",
+ "frequency"
+ ],
+ "fields": [
+ {
+ "default": "0",
+ "fieldname": "enable_youtube_tracking",
+ "fieldtype": "Check",
+ "label": "Enable YouTube Tracking"
+ },
+ {
+ "depends_on": "eval:doc.enable_youtube_tracking",
+ "fieldname": "api_key",
+ "fieldtype": "Data",
+ "label": "API Key",
+ "mandatory_depends_on": "eval:doc.enable_youtube_tracking"
+ },
+ {
+ "default": "1 hr",
+ "depends_on": "eval:doc.enable_youtube_tracking",
+ "fieldname": "frequency",
+ "fieldtype": "Select",
+ "label": "Frequency",
+ "mandatory_depends_on": "eval:doc.enable_youtube_tracking",
+ "options": "30 mins\n1 hr\n6 hrs\nDaily"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2020-09-07 16:09:00.360668",
+ "modified_by": "Administrator",
+ "module": "Utilities",
+ "name": "Video Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/utilities/doctype/video_settings/video_settings.py b/erpnext/utilities/doctype/video_settings/video_settings.py
new file mode 100644
index 0000000..36fb54f
--- /dev/null
+++ b/erpnext/utilities/doctype/video_settings/video_settings.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from apiclient.discovery import build
+
+class VideoSettings(Document):
+ def validate(self):
+ self.validate_youtube_api_key()
+
+ def validate_youtube_api_key(self):
+ if self.enable_youtube_tracking and self.api_key:
+ try:
+ build("youtube", "v3", developerKey=self.api_key)
+ except Exception:
+ title = _("Failed to Authenticate the API key.")
+ frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
+ frappe.throw(title + " Please check the error logs.", title=_("Invalid Credentials"))
\ No newline at end of file
diff --git a/erpnext/crm/report/minutes_to_first_response_for_opportunity/__init__.py b/erpnext/utilities/report/__init__.py
similarity index 100%
copy from erpnext/crm/report/minutes_to_first_response_for_opportunity/__init__.py
copy to erpnext/utilities/report/__init__.py
diff --git a/erpnext/support/report/minutes_to_first_response_for_issues/__init__.py b/erpnext/utilities/report/youtube_interactions/__init__.py
similarity index 100%
copy from erpnext/support/report/minutes_to_first_response_for_issues/__init__.py
copy to erpnext/utilities/report/youtube_interactions/__init__.py
diff --git a/erpnext/utilities/report/youtube_interactions/youtube_interactions.js b/erpnext/utilities/report/youtube_interactions/youtube_interactions.js
new file mode 100644
index 0000000..6e3e4e6
--- /dev/null
+++ b/erpnext/utilities/report/youtube_interactions/youtube_interactions.js
@@ -0,0 +1,20 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["YouTube Interactions"] = {
+ "filters": [
+ {
+ fieldname: "from_date",
+ label: __("From Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.now_date(), -12),
+ },
+ {
+ fieldname:"to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.now_date(),
+ }
+ ]
+};
diff --git a/erpnext/utilities/report/youtube_interactions/youtube_interactions.json b/erpnext/utilities/report/youtube_interactions/youtube_interactions.json
new file mode 100644
index 0000000..a40247b
--- /dev/null
+++ b/erpnext/utilities/report/youtube_interactions/youtube_interactions.json
@@ -0,0 +1,27 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-08-02 05:05:00.457093",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-08-02 05:05:00.457093",
+ "modified_by": "Administrator",
+ "module": "Utilities",
+ "name": "YouTube Interactions",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Video",
+ "report_name": "YouTube Interactions",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "All"
+ },
+ {
+ "role": "System Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/utilities/report/youtube_interactions/youtube_interactions.py b/erpnext/utilities/report/youtube_interactions/youtube_interactions.py
new file mode 100644
index 0000000..3516a35
--- /dev/null
+++ b/erpnext/utilities/report/youtube_interactions/youtube_interactions.py
@@ -0,0 +1,113 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import flt
+
+def execute(filters=None):
+ if not frappe.db.get_single_value("Video Settings", "enable_youtube_tracking") or not filters:
+ return [], []
+
+ columns = get_columns()
+ data = get_data(filters)
+ chart_data, summary = get_chart_summary_data(data)
+ return columns, data, None, chart_data, summary
+
+def get_columns():
+ return [
+ {
+ "label": _("Published Date"),
+ "fieldname": "publish_date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Title"),
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "width": 200
+ },
+ {
+ "label": _("Duration"),
+ "fieldname": "duration",
+ "fieldtype": "Duration",
+ "width": 100
+ },
+ {
+ "label": _("Views"),
+ "fieldname": "view_count",
+ "fieldtype": "Float",
+ "width": 200
+ },
+ {
+ "label": _("Likes"),
+ "fieldname": "like_count",
+ "fieldtype": "Float",
+ "width": 200
+ },
+ {
+ "label": _("Dislikes"),
+ "fieldname": "dislike_count",
+ "fieldtype": "Float",
+ "width": 100
+ },
+ {
+ "label": _("Comments"),
+ "fieldname": "comment_count",
+ "fieldtype": "Float",
+ "width": 100
+ }
+ ]
+
+def get_data(filters):
+ return frappe.db.sql("""
+ SELECT
+ publish_date, title, provider, duration,
+ view_count, like_count, dislike_count, comment_count
+ FROM `tabVideo`
+ WHERE view_count is not null
+ and publish_date between %(from_date)s and %(to_date)s
+ ORDER BY view_count desc""", filters, as_dict=1)
+
+def get_chart_summary_data(data):
+ labels, likes, views = [], [], []
+ total_views = 0
+
+ for row in data:
+ labels.append(row.get('title'))
+ likes.append(row.get('like_count'))
+ views.append(row.get('view_count'))
+ total_views += flt(row.get('view_count'))
+
+
+ chart_data = {
+ "data" : {
+ "labels" : labels,
+ "datasets" : [
+ {
+ "name" : "Likes",
+ "values" : likes
+ },
+ {
+ "name" : "Views",
+ "values" : views
+ }
+ ]
+ },
+ "type": "bar",
+ "barOptions": {
+ "stacked": 1
+ },
+ }
+
+ summary = [
+ {
+ "value": total_views,
+ "indicator": "Blue",
+ "label": "Total Views",
+ "datatype": "Float",
+ }
+ ]
+ return chart_data, summary
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 912d61f..c4f9171 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,10 +3,11 @@
gocardless-pro==1.11.0
googlemaps==3.1.1
pandas==1.0.5
-plaid-python==3.4.0
+plaid-python==6.0.0
pycountry==19.8.18
PyGithub==1.44.1
python-stdnum==1.12
+python-youtube==0.6.0
taxjar==1.9.0
tweepy==3.8.0
Unidecode==1.1.1