Merge pull request #15371 from pratu16x7/marketplace-fixes
[hub] add Item Manager permission to Hub Tracked Item
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index e040b61..b7eb786 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -302,6 +302,37 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "allow_cost_center_in_entry_of_bs_account",
+ "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": "Allow Cost Center In Entry of Balance Sheet Account",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "print_settings",
"fieldtype": "Section Break",
"hidden": 0,
@@ -543,6 +574,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -575,6 +607,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
}
],
@@ -589,7 +622,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-02-21 16:47:38.043115",
+ "modified": "2018-05-14 15:58:27.638576",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -597,7 +630,6 @@
"permissions": [
{
"amend": 0,
- "apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
@@ -617,7 +649,6 @@
},
{
"amend": 0,
- "apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
@@ -637,7 +668,6 @@
},
{
"amend": 0,
- "apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index 4f1570c..b834a5f 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -17,6 +17,7 @@
def validate(self):
self.validate_stale_days()
self.enable_payment_schedule_in_print()
+ self.enable_fields_for_cost_center_settings()
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
@@ -28,4 +29,9 @@
show_in_print = cint(self.show_payment_schedule_in_print)
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check")
- make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check")
\ No newline at end of file
+ make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check")
+
+ def enable_fields_for_cost_center_settings(self):
+ show_field = 0 if cint(self.allow_cost_center_in_entry_of_bs_account) else 1
+ for doctype in ("Sales Invoice", "Purchase Invoice", "Payment Entry"):
+ make_property_setter(doctype, "cost_center", "hidden", show_field, "Check")
diff --git a/erpnext/accounts/doctype/cashier_closing/__init__.py b/erpnext/accounts/doctype/cashier_closing/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/cashier_closing/__init__.py
diff --git a/erpnext/accounts/doctype/cashier_closing/cashier_closing.js b/erpnext/accounts/doctype/cashier_closing/cashier_closing.js
new file mode 100644
index 0000000..ce791e4
--- /dev/null
+++ b/erpnext/accounts/doctype/cashier_closing/cashier_closing.js
@@ -0,0 +1,11 @@
+// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+// License: GNU General Public License v3. See license.txt
+
+frappe.ui.form.on('Cashier Closing', {
+
+ setup: function(frm){
+ if (frm.doc.user == "" || frm.doc.user == null) {
+ frm.doc.user = frappe.session.user;
+ }
+ }
+});
diff --git a/erpnext/accounts/doctype/cashier_closing/cashier_closing.json b/erpnext/accounts/doctype/cashier_closing/cashier_closing.json
new file mode 100644
index 0000000..57a9c7a
--- /dev/null
+++ b/erpnext/accounts/doctype/cashier_closing/cashier_closing.json
@@ -0,0 +1,403 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "naming_series:",
+ "beta": 0,
+ "creation": "2018-06-18 16:51:49.994750",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "Cashier-closing-",
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_global_search": 1,
+ "in_list_view": 0,
+ "in_standard_filter": 1,
+ "label": "Series",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Cashier-closing-\n",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "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,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "user",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "User",
+ "length": 0,
+ "no_copy": 0,
+ "options": "User",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "Today",
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 1,
+ "label": "Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "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,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "from_time",
+ "fieldtype": "Time",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 1,
+ "label": "From Time",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "",
+ "fieldname": "time",
+ "fieldtype": "Time",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 1,
+ "label": "To Time",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0.00",
+ "fieldname": "expense",
+ "fieldtype": "Float",
+ "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": "Expense",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "2",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0.00",
+ "fieldname": "custody",
+ "fieldtype": "Float",
+ "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": "Custody",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "2",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0.00",
+ "fieldname": "outstanding_amount",
+ "fieldtype": "Float",
+ "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": "Outstanding Amount",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "2",
+ "print_hide": 0,
+ "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,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0.0",
+ "fieldname": "payments",
+ "fieldtype": "Table",
+ "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": "Payments",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Cashier Closing Payments",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "net_amount",
+ "fieldtype": "Float",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Net Amount",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "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,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 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": "Cashier Closing",
+ "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,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 1,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2018-09-03 10:59:54.500567",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Cashier Closing",
+ "name_case": "",
+ "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": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
new file mode 100644
index 0000000..906bc7f
--- /dev/null
+++ b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+from frappe.utils import cint, flt, cstr
+from frappe import _, msgprint, throw
+
+class CashierClosing(Document):
+ def validate(self):
+ self.validate_time()
+
+ def before_save(self):
+ self.get_outstanding()
+ self.make_calculations()
+
+ def get_outstanding(self):
+ values = frappe.db.sql("""
+ select sum(outstanding_amount)
+ from `tabSales Invoice`
+ where posting_date=%s and posting_time>=%s and posting_time<=%s and owner=%s
+ """, (self.date, self.from_time, self.time, self.user))
+ self.outstanding_amount = flt(values[0][0] if values else 0)
+
+ def make_calculations(self):
+ total = 0.00
+ for i in self.payments:
+ total += flt(i.amount)
+
+ self.net_amount = total + self.outstanding_amount + self.expense - self.custody
+
+ def validate_time(self):
+ if self.from_time >= self.time:
+ frappe.throw(_("From Time Should Be Less Than To Time"))
diff --git a/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js b/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js
new file mode 100644
index 0000000..a7fcc8d
--- /dev/null
+++ b/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Cashier Closing", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Cashier Closing
+ () => frappe.tests.make('Cashier Closing', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.py b/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.py
new file mode 100644
index 0000000..3c489a7
--- /dev/null
+++ b/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestCashierClosing(unittest.TestCase):
+ pass
diff --git a/erpnext/accounts/doctype/cashier_closing_payments/__init__.py b/erpnext/accounts/doctype/cashier_closing_payments/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/cashier_closing_payments/__init__.py
diff --git a/erpnext/accounts/doctype/cashier_closing_payments/cashier_closing_payments.json b/erpnext/accounts/doctype/cashier_closing_payments/cashier_closing_payments.json
new file mode 100644
index 0000000..bdfc70f
--- /dev/null
+++ b/erpnext/accounts/doctype/cashier_closing_payments/cashier_closing_payments.json
@@ -0,0 +1,103 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2018-09-02 14:45:36.303520",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "mode_of_payment",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Mode of Payment",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Mode of Payment",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0.00",
+ "fieldname": "amount",
+ "fieldtype": "Float",
+ "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": "Amount",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "2",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2018-09-02 14:45:36.303520",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Cashier Closing Payments",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cashier_closing_payments/cashier_closing_payments.py b/erpnext/accounts/doctype/cashier_closing_payments/cashier_closing_payments.py
new file mode 100644
index 0000000..f737031
--- /dev/null
+++ b/erpnext/accounts/doctype/cashier_closing_payments/cashier_closing_payments.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class CashierClosingPayments(Document):
+ pass
diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py
index dab5028..c4fad75 100644
--- a/erpnext/accounts/doctype/cost_center/test_cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py
@@ -4,4 +4,23 @@
import frappe
-test_records = frappe.get_test_records('Cost Center')
\ No newline at end of file
+test_records = frappe.get_test_records('Cost Center')
+
+
+
+def create_cost_center(**args):
+ args = frappe._dict(args)
+ if args.cost_center_name:
+ company = args.company or "_Test Company"
+ company_abbr = frappe.db.get_value("Company", company, "abbr")
+ cc_name = args.cost_center_name + " - " + company_abbr
+ if not frappe.db.exists("Cost Center", cc_name):
+ cc = frappe.new_doc("Cost Center")
+ cc.company = args.company or "_Test Company"
+ cc.cost_center_name = args.cost_center_name
+ cc.is_group = args.is_group or 0
+ cc.parent_cost_center = args.parent_cost_center or "_Test Company - _TC"
+ cc.insert()
+
+
+
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 47e214e..e6fe6ca 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -67,7 +67,8 @@
frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.")
.format(self.voucher_type, self.voucher_no, self.account))
else:
- if self.cost_center:
+ from erpnext.accounts.utils import get_allow_cost_center_in_entry_of_bs_account
+ if not get_allow_cost_center_in_entry_of_bs_account() and self.cost_center:
self.cost_center = None
if self.project:
self.project = None
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 6ad1df5..6aec6e9 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -414,37 +414,18 @@
args: {
company: frm.doc.company,
party_type: d.party_type,
- party: d.party
+ party: d.party,
+ cost_center: d.cost_center
}
});
}
},
+ cost_center: function(frm, dt, dn) {
+ erpnext.journal_entry.set_account_balance(frm, dt, dn);
+ },
account: function(frm, dt, dn) {
- var d = locals[dt][dn];
- if(d.account) {
- if(!frm.doc.company) frappe.throw(__("Please select Company first"));
- if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first"));
-
- return frappe.call({
- method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type",
- args: {
- account: d.account,
- date: frm.doc.posting_date,
- company: frm.doc.company,
- debit: flt(d.debit_in_account_currency),
- credit: flt(d.credit_in_account_currency),
- exchange_rate: d.exchange_rate
- },
- callback: function(r) {
- if(r.message) {
- $.extend(d, r.message);
- erpnext.journal_entry.set_debit_credit_in_company_currency(frm, dt, dn);
- refresh_field('accounts');
- }
- }
- });
- }
+ erpnext.journal_entry.set_account_balance(frm, dt, dn);
},
debit_in_account_currency: function(frm, cdt, cdn) {
@@ -637,3 +618,33 @@
cur_frm.reload_doc();
}
});
+
+$.extend(erpnext.journal_entry, {
+ set_account_balance: function(frm, dt, dn) {
+ var d = locals[dt][dn];
+ if(d.account) {
+ if(!frm.doc.company) frappe.throw(__("Please select Company first"));
+ if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first"));
+
+ return frappe.call({
+ method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type",
+ args: {
+ account: d.account,
+ date: frm.doc.posting_date,
+ company: frm.doc.company,
+ debit: flt(d.debit_in_account_currency),
+ credit: flt(d.credit_in_account_currency),
+ exchange_rate: d.exchange_rate,
+ cost_center: d.cost_center
+ },
+ callback: function(r) {
+ if(r.message) {
+ $.extend(d, r.message);
+ erpnext.journal_entry.set_debit_credit_in_company_currency(frm, dt, dn);
+ refresh_field('accounts');
+ }
+ }
+ });
+ }
+ },
+});
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 84c165f..259172e 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -716,7 +716,7 @@
def get_payment_entry(ref_doc, args):
- cost_center = frappe.get_cached_value('Company', ref_doc.company, "cost_center")
+ cost_center = ref_doc.get("cost_center") or frappe.get_cached_value('Company', ref_doc.company, "cost_center")
exchange_rate = 1
if args.get("party_account"):
# Modified to include the posting date for which the exchange rate is required.
@@ -849,14 +849,14 @@
}
@frappe.whitelist()
-def get_party_account_and_balance(company, party_type, party):
+def get_party_account_and_balance(company, party_type, party, cost_center=None):
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
account = get_party_account(party_type, party, company)
- account_balance = get_balance_on(account=account)
- party_balance = get_balance_on(party_type=party_type, party=party, company=company)
+ account_balance = get_balance_on(account=account, cost_center=cost_center)
+ party_balance = get_balance_on(party_type=party_type, party=party, company=company, cost_center=cost_center)
return {
"account": account,
@@ -867,7 +867,7 @@
@frappe.whitelist()
-def get_account_balance_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None):
+def get_account_balance_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None):
"""Returns dict of account balance and party type to be set in Journal Entry on selection of account."""
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
@@ -886,7 +886,7 @@
party_type = ""
grid_values = {
- "balance": get_balance_on(account, date),
+ "balance": get_balance_on(account, date, cost_center=cost_center),
"party_type": party_type,
"account_type": account_details.account_type,
"account_currency": account_details.account_currency or company_currency,
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 5495c93..6996c77 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -204,12 +204,72 @@
self.assertEqual(jv.inter_company_journal_entry_reference, "")
self.assertEqual(jv1.inter_company_journal_entry_reference, "")
+ def test_jv_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
+ accounts_settings.save()
+ cost_center = "_Test Cost Center for BS Account - _TC"
+ create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
+ jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False)
+ jv.voucher_type = "Bank Entry"
+ jv.multi_currency = 0
+ jv.cheque_no = "112233"
+ jv.cheque_date = nowdate()
+ jv.insert()
+ jv.submit()
+
+ expected_values = {
+ "_Test Cash - _TC": {
+ "cost_center": cost_center
+ },
+ "_Test Bank - _TC": {
+ "cost_center": cost_center
+ }
+ }
+
+ gl_entries = frappe.db.sql("""select account, cost_center, debit, credit
+ from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
+ order by account asc""", jv.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+
+ def test_jv_account_and_party_balance_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+ from erpnext.accounts.utils import get_balance_on
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
+ accounts_settings.save()
+ cost_center = "_Test Cost Center for BS Account - _TC"
+ create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
+ jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False)
+ account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
+ jv.voucher_type = "Bank Entry"
+ jv.multi_currency = 0
+ jv.cheque_no = "112233"
+ jv.cheque_date = nowdate()
+ jv.insert()
+ jv.submit()
+
+ expected_account_balance = account_balance - 100
+ account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
+ self.assertEqual(expected_account_balance, account_balance)
+
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+
def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False, project=None):
if not cost_center:
cost_center = "_Test Cost Center - _TC"
jv = frappe.new_doc("Journal Entry")
- jv.posting_date = posting_date or "2013-02-14"
+ jv.posting_date = posting_date or nowdate()
jv.company = "_Test Company"
jv.user_remark = "test"
jv.multi_currency = 1
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 490f2b4..9215e5f 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -245,7 +245,8 @@
company: frm.doc.company,
party_type: frm.doc.party_type,
party: frm.doc.party,
- date: frm.doc.posting_date
+ date: frm.doc.posting_date,
+ cost_center: frm.doc.cost_center
},
callback: function(r, rt) {
if(r.message) {
@@ -317,7 +318,8 @@
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
args: {
"account": account,
- "date": frm.doc.posting_date
+ "date": frm.doc.posting_date,
+ "cost_center": frm.doc.cost_center
},
callback: function(r, rt) {
if(r.message) {
@@ -505,7 +507,8 @@
"party_type": frm.doc.party_type,
"payment_type": frm.doc.payment_type,
"party": frm.doc.party,
- "party_account": frm.doc.payment_type=="Receive" ? frm.doc.paid_from : frm.doc.paid_to
+ "party_account": frm.doc.payment_type=="Receive" ? frm.doc.paid_from : frm.doc.paid_to,
+ "cost_center": frm.doc.cost_center
}
},
callback: function(r, rt) {
@@ -859,3 +862,38 @@
frm.events.set_unallocated_amount(frm);
}
})
+frappe.ui.form.on('Payment Entry', {
+ cost_center: function(frm){
+ if (frm.doc.posting_date && (frm.doc.paid_from||frm.doc.paid_to)) {
+ return frappe.call({
+ method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_and_account_balance",
+ args: {
+ company: frm.doc.company,
+ date: frm.doc.posting_date,
+ paid_from: frm.doc.paid_from,
+ paid_to: frm.doc.paid_to,
+ ptype: frm.doc.party_type,
+ pty: frm.doc.party,
+ cost_center: frm.doc.cost_center
+ },
+ callback: function(r, rt) {
+ if(r.message) {
+ frappe.run_serially([
+ () => {
+ frm.set_value("paid_from_account_balance", r.message.paid_from_account_balance);
+ frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance);
+ frm.set_value("party_balance", r.message.party_balance);
+ },
+ () => {
+ if(frm.doc.payment_type != "Internal") {
+ frm.events.get_outstanding_documents(frm);
+ }
+ }
+ ]);
+
+ }
+ }
+ });
+ }
+ },
+})
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index bffe669..daf2f2f 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -215,6 +215,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "cost_center",
+ "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": "Cost Center",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Cost Center",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "mode_of_payment",
"fieldtype": "Link",
"hidden": 0,
@@ -1906,7 +1939,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-21 15:44:28.647566",
+ "modified": "2018-09-07 15:44:28.647566",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
@@ -1962,4 +1995,4 @@
"track_changes": 1,
"track_seen": 0,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 9ce7ecb..6c814ad 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -127,12 +127,12 @@
self.party_account = party_account
if self.paid_from and not (self.paid_from_account_currency or self.paid_from_account_balance):
- acc = get_account_details(self.paid_from, self.posting_date)
+ acc = get_account_details(self.paid_from, self.posting_date, self.cost_center)
self.paid_from_account_currency = acc.account_currency
self.paid_from_account_balance = acc.account_balance
if self.paid_to and not (self.paid_to_account_currency or self.paid_to_account_balance):
- acc = get_account_details(self.paid_to, self.posting_date)
+ acc = get_account_details(self.paid_to, self.posting_date, self.cost_center)
self.paid_to_account_currency = acc.account_currency
self.paid_to_account_balance = acc.account_balance
@@ -419,7 +419,8 @@
"party_type": self.party_type,
"party": self.party,
"against": against_account,
- "account_currency": self.party_account_currency
+ "account_currency": self.party_account_currency,
+ "cost_center": self.cost_center
})
dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit"
@@ -462,7 +463,8 @@
"account_currency": self.paid_from_account_currency,
"against": self.party if self.payment_type=="Pay" else self.paid_to,
"credit_in_account_currency": self.paid_amount,
- "credit": self.base_paid_amount
+ "credit": self.base_paid_amount,
+ "cost_center": self.cost_center
})
)
if self.payment_type in ("Receive", "Internal Transfer"):
@@ -472,7 +474,8 @@
"account_currency": self.paid_to_account_currency,
"against": self.party if self.payment_type=="Receive" else self.paid_from,
"debit_in_account_currency": self.received_amount,
- "debit": self.base_received_amount
+ "debit": self.base_received_amount,
+ "cost_center": self.cost_center
})
)
@@ -549,6 +552,10 @@
condition = " and voucher_type='{0}' and voucher_no='{1}'"\
.format(frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"]))
+ # Add cost center condition
+ if args.get("cost_center"):
+ condition += " and cost_center='%s'" % args.get("cost_center")
+
outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
args.get("party_account"), condition=condition)
@@ -573,7 +580,7 @@
return negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
-def get_orders_to_be_billed(posting_date, party_type, party, party_account_currency, company_currency):
+def get_orders_to_be_billed(posting_date, party_type, party, party_account_currency, company_currency, cost_center=None):
if party_type == "Customer":
voucher_type = 'Sales Order'
elif party_type == "Supplier":
@@ -581,6 +588,12 @@
elif party_type == "Employee":
voucher_type = None
+ # Add cost center condition
+ doc = frappe.get_doc({"doctype": voucher_type})
+ condition = ""
+ if doc and hasattr(doc, 'cost_center'):
+ condition = " and cost_center='%s'" % cost_center
+
orders = []
if voucher_type:
ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"
@@ -599,12 +612,14 @@
and ifnull(status, "") != "Closed"
and {ref_field} > advance_paid
and abs(100 - per_billed) > 0.01
+ {condition}
order by
transaction_date, name
""".format(**{
"ref_field": ref_field,
"voucher_type": voucher_type,
- "party_type": scrub(party_type)
+ "party_type": scrub(party_type),
+ "condition": condition
}), party, as_dict=True)
order_list = []
@@ -616,7 +631,7 @@
return order_list
-def get_negative_outstanding_invoices(party_type, party, party_account, party_account_currency, company_currency):
+def get_negative_outstanding_invoices(party_type, party, party_account, party_account_currency, company_currency, cost_center=None):
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
supplier_condition = ""
if voucher_type == "Purchase Invoice":
@@ -647,22 +662,23 @@
"grand_total_field": grand_total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
- "party_account": "debit_to" if party_type == "Customer" else "credit_to"
+ "party_account": "debit_to" if party_type == "Customer" else "credit_to",
+ "cost_center": cost_center
}), (party, party_account), as_dict=True)
@frappe.whitelist()
-def get_party_details(company, party_type, party, date):
+def get_party_details(company, party_type, party, date, cost_center=None):
if not frappe.db.exists(party_type, party):
frappe.throw(_("Invalid {0}: {1}").format(party_type, party))
party_account = get_party_account(party_type, party, company)
account_currency = get_account_currency(party_account)
- account_balance = get_balance_on(party_account, date)
+ account_balance = get_balance_on(party_account, date, cost_center=cost_center)
_party_name = "title" if party_type == "Student" else party_type.lower() + "_name"
party_name = frappe.db.get_value(party_type, party, _party_name)
- party_balance = get_balance_on(party_type=party_type, party=party)
+ party_balance = get_balance_on(party_type=party_type, party=party, cost_center=cost_center)
return {
"party_account": party_account,
@@ -674,11 +690,11 @@
@frappe.whitelist()
-def get_account_details(account, date):
+def get_account_details(account, date, cost_center=None):
frappe.has_permission('Payment Entry', throw=True)
return frappe._dict({
"account_currency": get_account_currency(account),
- "account_balance": get_balance_on(account, date),
+ "account_balance": get_balance_on(account, date, cost_center=cost_center),
"account_type": frappe.db.get_value("Account", account, "account_type")
})
@@ -855,6 +871,7 @@
pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type
pe.company = doc.company
+ pe.cost_center = doc.get("cost_center")
pe.posting_date = nowdate()
pe.mode_of_payment = doc.get("mode_of_payment")
pe.party_type = party_type
@@ -912,4 +929,12 @@
and {dr_or_cr} > 0
""".format(dr_or_cr=dr_or_cr), (dt, dn, party_type, party, account, due_date))
- return paid_amount[0][0] if paid_amount else 0
\ No newline at end of file
+ return paid_amount[0][0] if paid_amount else 0
+
+@frappe.whitelist()
+def get_party_and_account_balance(company, date, paid_from, paid_to=None, ptype=None, pty=None, cost_center=None):
+ return frappe._dict({
+ "party_balance": get_balance_on(party_type=ptype, party=pty, cost_center=cost_center),
+ "paid_from_account_balance": get_balance_on(paid_from, date, cost_center=cost_center),
+ "paid_to_account_balance": get_balance_on(paid_to, date=date, cost_center=cost_center)
+ })
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 2408235..a7ab175 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -8,8 +8,8 @@
from frappe.utils import flt, nowdate
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry, InvalidPaymentEntry
-from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
-from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice, create_sales_invoice_against_cost_center
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice, make_purchase_invoice_against_cost_center
from erpnext.hr.doctype.expense_claim.test_expense_claim import make_expense_claim
test_dependencies = ["Item"]
@@ -322,7 +322,7 @@
self.assertTrue(gl_entries)
- for i, gle in enumerate(gl_entries):
+ for gle in gl_entries:
self.assertEqual(expected_gle[gle.account][0], gle.account)
self.assertEqual(expected_gle[gle.account][1], gle.debit)
self.assertEqual(expected_gle[gle.account][2], gle.credit)
@@ -394,3 +394,176 @@
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 0)
+
+ def test_payment_entry_against_sales_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
+ accounts_settings.save()
+ cost_center = "_Test Cost Center for BS Account - _TC"
+ create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
+
+ si = create_sales_invoice_against_cost_center(cost_center=cost_center, debit_to="Debtors - _TC")
+
+ pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
+ self.assertEqual(pe.cost_center, si.cost_center)
+
+ pe.reference_no = "112211-1"
+ pe.reference_date = nowdate()
+ pe.paid_to = "_Test Bank - _TC"
+ pe.paid_amount = si.grand_total
+ pe.insert()
+ pe.submit()
+
+ expected_values = {
+ "_Test Bank - _TC": {
+ "cost_center": cost_center
+ },
+ "Debtors - _TC": {
+ "cost_center": cost_center
+ }
+ }
+
+ gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
+ order by account asc""", pe.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+
+ def test_payment_entry_against_sales_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+ si = create_sales_invoice(debit_to="Debtors - _TC")
+
+ pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
+
+ pe.reference_no = "112211-2"
+ pe.reference_date = nowdate()
+ pe.paid_to = "_Test Bank - _TC"
+ pe.paid_amount = si.grand_total
+ pe.insert()
+ pe.submit()
+
+ gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
+ order by account asc""", pe.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(gle.cost_center, None)
+
+ def test_payment_entry_against_purchase_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
+ accounts_settings.save()
+ cost_center = "_Test Cost Center for BS Account - _TC"
+ create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
+
+ pi = make_purchase_invoice_against_cost_center(cost_center=cost_center, credit_to="Creditors - _TC")
+
+ pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
+ self.assertEqual(pe.cost_center, pi.cost_center)
+
+ pe.reference_no = "112222-1"
+ pe.reference_date = nowdate()
+ pe.paid_from = "_Test Bank - _TC"
+ pe.paid_amount = pi.grand_total
+ pe.insert()
+ pe.submit()
+
+ expected_values = {
+ "_Test Bank - _TC": {
+ "cost_center": cost_center
+ },
+ "Creditors - _TC": {
+ "cost_center": cost_center
+ }
+ }
+
+ gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
+ order by account asc""", pe.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+
+ def test_payment_entry_against_purchase_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+ pi = make_purchase_invoice(credit_to="Creditors - _TC")
+
+ pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
+
+ pe.reference_no = "112222-2"
+ pe.reference_date = nowdate()
+ pe.paid_from = "_Test Bank - _TC"
+ pe.paid_amount = pi.grand_total
+ pe.insert()
+ pe.submit()
+
+ gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
+ order by account asc""", pe.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(gle.cost_center, None)
+
+ def test_payment_entry_account_and_party_balance_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+ from erpnext.accounts.utils import get_balance_on
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
+ accounts_settings.save()
+ cost_center = "_Test Cost Center for BS Account - _TC"
+ create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
+
+ si = create_sales_invoice_against_cost_center(cost_center=cost_center, debit_to="Debtors - _TC")
+
+ account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=si.cost_center)
+ party_balance = get_balance_on(party_type="Customer", party=si.customer, cost_center=si.cost_center)
+ party_account_balance = get_balance_on(si.debit_to, cost_center=si.cost_center)
+
+ pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
+ pe.reference_no = "112211-1"
+ pe.reference_date = nowdate()
+ pe.paid_to = "_Test Bank - _TC"
+ pe.paid_amount = si.grand_total
+ pe.insert()
+ pe.submit()
+
+ expected_account_balance = account_balance + si.grand_total
+ expected_party_balance = party_balance - si.grand_total
+ expected_party_account_balance = party_account_balance - si.grand_total
+
+ account_balance = get_balance_on(account=pe.paid_to, cost_center=pe.cost_center)
+ party_balance = get_balance_on(party_type="Customer", party=pe.party, cost_center=pe.cost_center)
+ party_account_balance = get_balance_on(account=pe.paid_from, cost_center=pe.cost_center)
+
+ self.assertEqual(pe.cost_center, si.cost_center)
+ self.assertEqual(expected_account_balance, account_balance)
+ self.assertEqual(expected_party_balance, party_balance)
+ self.assertEqual(expected_party_account_balance, party_account_balance)
+
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
index 83e3ba0..1b38904 100644
--- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
+++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
@@ -15,6 +15,7 @@
"fields": [
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -47,11 +48,12 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
- "fetch_from": "payment_term.description",
+ "fetch_from": "",
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
@@ -80,6 +82,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -112,11 +115,12 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
- "fetch_from": "payment_term.invoice_portion",
+ "fetch_from": "",
"fieldname": "invoice_portion",
"fieldtype": "Percent",
"hidden": 0,
@@ -145,6 +149,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -177,6 +182,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -218,7 +224,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-05-25 22:43:31.890251",
+ "modified": "2018-09-06 17:35:44.580209",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
@@ -232,5 +238,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 7a1a182..2e82c01 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -525,4 +525,4 @@
}
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
}
-})
\ No newline at end of file
+})
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 2dd6e17..6c0ee8b 100755
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -386,6 +386,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "cost_center",
+ "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": "Cost Center",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Cost Center",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 2660490..273a6e4 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -389,6 +389,7 @@
if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
+ "cost_center": self.cost_center
}, self.party_account_currency)
)
@@ -472,7 +473,8 @@
"account": self.stock_received_but_not_billed,
"against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
- "remarks": self.remarks or "Accounting Entry for Stock"
+ "remarks": self.remarks or "Accounting Entry for Stock",
+ "cost_center": self.cost_center
})
)
@@ -500,7 +502,8 @@
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": base_asset_amount,
"debit_in_account_currency": (base_asset_amount
- if asset_rbnb_currency == self.company_currency else asset_amount)
+ if asset_rbnb_currency == self.company_currency else asset_amount),
+ "cost_center": item.cost_center
}))
if item.item_tax_amount:
@@ -526,7 +529,8 @@
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": base_asset_amount,
"debit_in_account_currency": (base_asset_amount
- if cwip_account_currency == self.company_currency else asset_amount)
+ if cwip_account_currency == self.company_currency else asset_amount),
+ "cost_center": self.cost_center
}))
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
@@ -626,6 +630,7 @@
if self.party_account_currency==self.company_currency else self.paid_amount,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
+ "cost_center": self.cost_center
}, self.party_account_currency)
)
@@ -635,7 +640,8 @@
"against": self.supplier,
"credit": self.base_paid_amount,
"credit_in_account_currency": self.base_paid_amount \
- if bank_account_currency==self.company_currency else self.paid_amount
+ if bank_account_currency==self.company_currency else self.paid_amount,
+ "cost_center": self.cost_center
}, bank_account_currency)
)
@@ -656,6 +662,7 @@
if self.party_account_currency==self.company_currency else self.write_off_amount,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
+ "cost_center": self.cost_center
}, self.party_account_currency)
)
gl_entries.append(
@@ -665,7 +672,7 @@
"credit": flt(self.base_write_off_amount),
"credit_in_account_currency": self.base_write_off_amount \
if write_off_account_currency==self.company_currency else self.write_off_amount,
- "cost_center": self.write_off_cost_center
+ "cost_center": self.cost_center or self.write_off_cost_center
})
)
@@ -680,7 +687,7 @@
"against": self.supplier,
"debit_in_account_currency": self.rounding_adjustment,
"debit": self.base_rounding_adjustment,
- "cost_center": round_off_cost_center,
+ "cost_center": self.cost_center or round_off_cost_center,
}
))
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 8cf6b1a..c8c23c7 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -790,6 +790,66 @@
pi_doc = frappe.get_doc('Purchase Invoice', pi.name)
self.assertEqual(pi_doc.outstanding_amount, 0)
+ def test_purchase_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
+ accounts_settings.save()
+ cost_center = "_Test Cost Center for BS Account - _TC"
+ create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
+
+ pi = make_purchase_invoice_against_cost_center(cost_center=cost_center, credit_to="Creditors - _TC")
+ self.assertEqual(pi.cost_center, cost_center)
+
+ expected_values = {
+ "Creditors - _TC": {
+ "cost_center": cost_center
+ },
+ "_Test Account Cost for Goods Sold - _TC": {
+ "cost_center": cost_center
+ }
+ }
+
+ gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
+ order by account asc""", pi.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+
+ def test_purchase_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+ cost_center = "_Test Cost Center - _TC"
+ pi = make_purchase_invoice(credit_to="Creditors - _TC")
+
+ expected_values = {
+ "Creditors - _TC": {
+ "cost_center": None
+ },
+ "_Test Account Cost for Goods Sold - _TC": {
+ "cost_center": cost_center
+ }
+ }
+
+ gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
+ order by account asc""", pi.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+
def unlink_payment_on_cancel_of_invoice(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.unlink_payment_on_cancellation_of_invoice = enable
@@ -839,4 +899,50 @@
pi.submit()
return pi
-test_records = frappe.get_test_records('Purchase Invoice')
\ No newline at end of file
+def make_purchase_invoice_against_cost_center(**args):
+ pi = frappe.new_doc("Purchase Invoice")
+ args = frappe._dict(args)
+ pi.posting_date = args.posting_date or today()
+ if args.posting_time:
+ pi.posting_time = args.posting_time
+ if args.update_stock:
+ pi.update_stock = 1
+ if args.is_paid:
+ pi.is_paid = 1
+
+ if args.cash_bank_account:
+ pi.cash_bank_account=args.cash_bank_account
+
+ pi.company = args.company or "_Test Company"
+ pi.cost_center = args.cost_center or "_Test Cost Center - _TC"
+ pi.supplier = args.supplier or "_Test Supplier"
+ pi.currency = args.currency or "INR"
+ pi.conversion_rate = args.conversion_rate or 1
+ pi.is_return = args.is_return
+ pi.is_return = args.is_return
+ pi.credit_to = args.return_against or "Creditors - _TC"
+ pi.is_subcontracted = args.is_subcontracted or "No"
+ pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
+
+ pi.append("items", {
+ "item_code": args.item or args.item_code or "_Test Item",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty or 5,
+ "received_qty": args.received_qty or 0,
+ "rejected_qty": args.rejected_qty or 0,
+ "rate": args.rate or 50,
+ "conversion_factor": 1.0,
+ "serial_no": args.serial_no,
+ "stock_uom": "_Test UOM",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
+ "project": args.project,
+ "rejected_warehouse": args.rejected_warehouse or "",
+ "rejected_serial_no": args.rejected_serial_no or ""
+ })
+ if not args.do_not_save:
+ pi.insert()
+ if not args.do_not_submit:
+ pi.submit()
+ return pi
+
+test_records = frappe.get_test_records('Purchase Invoice')
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index dc201b0..b2044ab 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -6,7 +6,6 @@
{% include 'erpnext/selling/sales_common.js' %};
-cur_frm.add_fetch('customer', 'tax_id', 'tax_id');
frappe.provide("erpnext.accounts");
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
@@ -16,7 +15,7 @@
},
onload: function() {
var me = this;
- this._super();
+ this._super();
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
@@ -466,7 +465,7 @@
}
}
-//project name
+// project name
//--------------------------
cur_frm.fields_dict['project'].get_query = function(doc, cdt, cdn) {
return{
@@ -543,7 +542,10 @@
frappe.ui.form.on('Sales Invoice', {
setup: function(frm){
-
+ frm.add_fetch('customer', 'tax_id', 'tax_id');
+ frm.add_fetch('payment_term', 'invoice_portion', 'invoice_portion');
+ frm.add_fetch('payment_term', 'description', 'description');
+
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Sales Return',
@@ -625,7 +627,7 @@
}
};
},
- //When multiple companies are set up. in case company name is changed set default company address
+ // When multiple companies are set up. in case company name is changed set default company address
company:function(frm){
if (frm.doc.company)
{
@@ -712,8 +714,52 @@
}
frm.set_value("loyalty_amount", loyalty_amount);
}
- }
+ },
+ // Healthcare
+ patient: function(frm) {
+ if (frappe.boot.active_domains.includes("Healthcare")){
+ if(frm.doc.patient){
+ frappe.call({
+ method: "frappe.client.get_value",
+ args:{
+ doctype: "Patient",
+ filters: {"name": frm.doc.patient},
+ fieldname: "customer"
+ },
+ callback:function(patient_customer) {
+ if(patient_customer){
+ frm.set_value("customer", patient_customer.message.customer);
+ frm.refresh_fields();
+ }
+ }
+ });
+ }
+ else{
+ frm.set_value("customer", '');
+ }
+ }
+ },
+ refresh: function(frm) {
+ if (frappe.boot.active_domains.includes("Healthcare")){
+ frm.set_df_property("patient", "hidden", 0);
+ frm.set_df_property("patient_name", "hidden", 0);
+ frm.set_df_property("ref_practitioner", "hidden", 0);
+ if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) {
+ frm.add_custom_button(__('Healthcare Services'), function() {
+ get_healthcare_services_to_invoice(frm);
+ },"Get items from");
+ frm.add_custom_button(__('Prescriptions'), function() {
+ get_drugs_to_invoice(frm);
+ },"Get items from");
+ }
+ }
+ else{
+ frm.set_df_property("patient", "hidden", 1);
+ frm.set_df_property("patient_name", "hidden", 1);
+ frm.set_df_property("ref_practitioner", "hidden", 1);
+ }
+ }
})
frappe.ui.form.on('Sales Invoice Timesheet', {
@@ -816,3 +862,270 @@
dialog.show();
}
+
+// Healthcare
+var get_healthcare_services_to_invoice = function(frm) {
+ var me = this;
+ let selected_patient = '';
+ var dialog = new frappe.ui.Dialog({
+ title: __("Get Items from Healthcare Services"),
+ fields:[
+ {
+ fieldtype: 'Link',
+ options: 'Patient',
+ label: 'Patient',
+ fieldname: "patient",
+ reqd: true
+ },
+ { fieldtype: 'Section Break' },
+ { fieldtype: 'HTML', fieldname: 'results_area' }
+ ]
+ });
+ var $wrapper;
+ var $results;
+ var $placeholder;
+ dialog.set_values({
+ 'patient': frm.doc.patient
+ });
+ dialog.fields_dict["patient"].df.onchange = () => {
+ var patient = dialog.fields_dict.patient.input.value;
+ if(patient && patient!=selected_patient){
+ selected_patient = patient;
+ var method = "erpnext.healthcare.utils.get_healthcare_services_to_invoice";
+ var args = {patient: patient};
+ var columns = (["service", "reference_name", "reference_type"]);
+ get_healthcare_items(frm, true, $results, $placeholder, method, args, columns);
+ }
+ else if(!patient){
+ selected_patient = '';
+ $results.empty();
+ $results.append($placeholder);
+ }
+ }
+ $wrapper = dialog.fields_dict.results_area.$wrapper.append(`<div class="results"
+ style="border: 1px solid #d1d8dd; border-radius: 3px; height: 300px; overflow: auto;"></div>`);
+ $results = $wrapper.find('.results');
+ $placeholder = $(`<div class="multiselect-empty-state">
+ <span class="text-center" style="margin-top: -40px;">
+ <i class="fa fa-2x fa-heartbeat text-extra-muted"></i>
+ <p class="text-extra-muted">No billable Healthcare Services found</p>
+ </span>
+ </div>`);
+ $results.on('click', '.list-item--head :checkbox', (e) => {
+ $results.find('.list-item-container .list-row-check')
+ .prop("checked", ($(e.target).is(':checked')));
+ });
+ set_primary_action(frm, dialog, $results, true);
+ dialog.show();
+};
+
+var get_healthcare_items = function(frm, invoice_healthcare_services, $results, $placeholder, method, args, columns) {
+ var me = this;
+ $results.empty();
+ frappe.call({
+ method: method,
+ args: args,
+ callback: function(data) {
+ if(data.message){
+ $results.append(make_list_row(columns, invoice_healthcare_services));
+ for(let i=0; i<data.message.length; i++){
+ $results.append(make_list_row(columns, invoice_healthcare_services, data.message[i]));
+ }
+ }else {
+ $results.append($placeholder);
+ }
+ }
+ });
+}
+
+var make_list_row= function(columns, invoice_healthcare_services, result={}) {
+ var me = this;
+ // Make a head row by default (if result not passed)
+ let head = Object.keys(result).length === 0;
+ let contents = ``;
+ columns.forEach(function(column) {
+ contents += `<div class="list-item__content ellipsis">
+ ${
+ head ? `<span class="ellipsis">${__(frappe.model.unscrub(column))}</span>`
+
+ :(column !== "name" ? `<span class="ellipsis">${__(result[column])}</span>`
+ : `<a class="list-id ellipsis">
+ ${__(result[column])}</a>`)
+ }
+ </div>`;
+ })
+
+ let $row = $(`<div class="list-item">
+ <div class="list-item__content" style="flex: 0 0 10px;">
+ <input type="checkbox" class="list-row-check" ${result.checked ? 'checked' : ''}>
+ </div>
+ ${contents}
+ </div>`);
+
+ $row = list_row_data_items(head, $row, result, invoice_healthcare_services);
+ return $row;
+};
+
+var set_primary_action= function(frm, dialog, $results, invoice_healthcare_services) {
+ var me = this;
+ dialog.set_primary_action(__('Add'), function() {
+ let checked_values = get_checked_values($results);
+ if(checked_values.length > 0){
+ frm.set_value("patient", dialog.fields_dict.patient.input.value);
+ frm.set_value("items", []);
+ add_to_item_line(frm, checked_values, invoice_healthcare_services);
+ dialog.hide();
+ }
+ else{
+ if(invoice_healthcare_services){
+ frappe.msgprint(__("Please select Healthcare Service"));
+ }
+ else{
+ frappe.msgprint(__("Please select Drug"));
+ }
+ }
+ });
+};
+
+var get_checked_values= function($results) {
+ return $results.find('.list-item-container').map(function() {
+ let checked_values = {};
+ if ($(this).find('.list-row-check:checkbox:checked').length > 0 ) {
+ checked_values['dn'] = $(this).attr('data-dn');
+ checked_values['dt'] = $(this).attr('data-dt');
+ checked_values['item'] = $(this).attr('data-item');
+ if($(this).attr('data-rate') != 'undefined'){
+ checked_values['rate'] = $(this).attr('data-rate');
+ }
+ else{
+ checked_values['rate'] = false;
+ }
+ if($(this).attr('data-income-account') != 'undefined'){
+ checked_values['income_account'] = $(this).attr('data-income-account');
+ }
+ else{
+ checked_values['income_account'] = false;
+ }
+ if($(this).attr('data-qty') != 'undefined'){
+ checked_values['qty'] = $(this).attr('data-qty');
+ }
+ else{
+ checked_values['qty'] = false;
+ }
+ if($(this).attr('data-description') != 'undefined'){
+ checked_values['description'] = $(this).attr('data-description');
+ }
+ else{
+ checked_values['description'] = false;
+ }
+ return checked_values;
+ }
+ }).get();
+};
+
+var get_drugs_to_invoice = function(frm) {
+ var me = this;
+ let selected_encounter = '';
+ var dialog = new frappe.ui.Dialog({
+ title: __("Get Items from Prescriptions"),
+ fields:[
+ { fieldtype: 'Link', options: 'Patient', label: 'Patient', fieldname: "patient", reqd: true },
+ { fieldtype: 'Link', options: 'Patient Encounter', label: 'Patient Encounter', fieldname: "encounter", reqd: true,
+ description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.',
+ get_query: function(doc) {
+ return {
+ filters: { patient :dialog.get_value("patient") }
+ };
+ }
+ },
+ { fieldtype: 'Section Break' },
+ { fieldtype: 'HTML', fieldname: 'results_area' }
+ ]
+ });
+ var $wrapper;
+ var $results;
+ var $placeholder;
+ dialog.set_values({
+ 'patient': frm.doc.patient,
+ 'encounter': ""
+ });
+ dialog.fields_dict["encounter"].df.onchange = () => {
+ var encounter = dialog.fields_dict.encounter.input.value;
+ if(encounter && encounter!=selected_encounter){
+ selected_encounter = encounter;
+ var method = "erpnext.healthcare.utils.get_drugs_to_invoice";
+ var args = {encounter: encounter};
+ var columns = (["drug_code", "quantity", "description"]);
+ get_healthcare_items(frm, false, $results, $placeholder, method, args, columns);
+ }
+ else if(!encounter){
+ selected_encounter = '';
+ $results.empty();
+ $results.append($placeholder);
+ }
+ }
+ $wrapper = dialog.fields_dict.results_area.$wrapper.append(`<div class="results"
+ style="border: 1px solid #d1d8dd; border-radius: 3px; height: 300px; overflow: auto;"></div>`);
+ $results = $wrapper.find('.results');
+ $placeholder = $(`<div class="multiselect-empty-state">
+ <span class="text-center" style="margin-top: -40px;">
+ <i class="fa fa-2x fa-heartbeat text-extra-muted"></i>
+ <p class="text-extra-muted">No Drug Prescription found</p>
+ </span>
+ </div>`);
+ $results.on('click', '.list-item--head :checkbox', (e) => {
+ $results.find('.list-item-container .list-row-check')
+ .prop("checked", ($(e.target).is(':checked')));
+ });
+ set_primary_action(frm, dialog, $results, false);
+ dialog.show();
+};
+
+var list_row_data_items = function(head, $row, result, invoice_healthcare_services) {
+ if(invoice_healthcare_services){
+ head ? $row.addClass('list-item--head')
+ : $row = $(`<div class="list-item-container"
+ data-dn= "${result.reference_name}" data-dt= "${result.reference_type}" data-item= "${result.service}"
+ data-rate = ${result.rate}
+ data-income-account = "${result.income_account}"
+ data-qty = ${result.qty}
+ data-description = "${result.description}">
+ </div>`).append($row);
+ }
+ else{
+ head ? $row.addClass('list-item--head')
+ : $row = $(`<div class="list-item-container"
+ data-item= "${result.drug_code}"
+ data-qty = ${result.quantity}
+ data-description = "${result.description}">
+ </div>`).append($row);
+ }
+ return $row
+};
+
+var add_to_item_line = function(frm, checked_values, invoice_healthcare_services){
+ if(invoice_healthcare_services){
+ frappe.call({
+ doc: frm.doc,
+ method: "set_healthcare_services",
+ args:{
+ checked_values: checked_values
+ },
+ callback: function() {
+ frm.trigger("validate");
+ frm.refresh_fields();
+ }
+ });
+ }
+ else{
+ for(let i=0; i<checked_values.length; i++){
+ var si_item = frappe.model.add_child(frm.doc, 'Sales Invoice Item', 'items');
+ frappe.model.set_value(si_item.doctype, si_item.name, 'item_code', checked_values[i]['item']);
+ frappe.model.set_value(si_item.doctype, si_item.name, 'qty', 1);
+ if(checked_values[i]['qty'] > 1){
+ frappe.model.set_value(si_item.doctype, si_item.name, 'qty', parseFloat(checked_values[i]['qty']));
+ }
+ }
+ frm.refresh_fields();
+ }
+};
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 05b8300..4154d2e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -101,7 +101,7 @@
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
- "options": "ACC-SINV-.YYYY.-\n",
+ "options": "ACC-SINV-.YYYY.-",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
@@ -451,6 +451,39 @@
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "cost_center",
+ "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": "Cost Center",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Cost Center",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
@@ -2808,7 +2841,7 @@
"label": "Apply Additional Discount On",
"length": 0,
"no_copy": 0,
- "options": "\nGrand Total\nNet Total\n",
+ "options": "\nGrand Total\nNet Total",
"permlevel": 0,
"precision": "",
"print_hide": 1,
@@ -4383,6 +4416,38 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "group_same_items",
+ "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": "Group same items",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -4662,7 +4727,7 @@
"label": "Status",
"length": 0,
"no_copy": 1,
- "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\n",
+ "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled",
"permlevel": 0,
"precision": "",
"print_hide": 1,
@@ -4834,7 +4899,7 @@
"no_copy": 0,
"oldfieldname": "is_opening",
"oldfieldtype": "Select",
- "options": "No\nYes\n",
+ "options": "No\nYes",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
@@ -4866,7 +4931,7 @@
"label": "C-Form Applicable",
"length": 0,
"no_copy": 1,
- "options": "No\nYes\n",
+ "options": "No\nYes",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
@@ -5481,7 +5546,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2018-08-29 16:23:03.940415",
+ "modified": "2018-09-07 14:24:58.854289",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -5575,5 +5640,6 @@
"timeline_field": "customer",
"title_field": "title",
"track_changes": 1,
- "track_seen": 1
+ "track_seen": 1,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index bdf8349..4eeedac 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -24,6 +24,8 @@
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
+from erpnext.healthcare.utils import manage_invoice_submit_cancel
+
from six import iteritems
form_grid_templates = {
@@ -179,6 +181,13 @@
if self.redeem_loyalty_points and self.loyalty_points:
self.apply_loyalty_points()
+ # Healthcare Service Invoice.
+ domain_settings = frappe.get_doc('Domain Settings')
+ active_domains = [d.domain for d in domain_settings.active_domains]
+
+ if "Healthcare" in active_domains:
+ manage_invoice_submit_cancel(self, "on_submit")
+
def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice."))
@@ -227,6 +236,13 @@
unlink_inter_company_invoice(self.doctype, self.name, self.inter_company_invoice_reference)
+ # Healthcare Service Invoice.
+ domain_settings = frappe.get_doc('Domain Settings')
+ active_domains = [d.domain for d in domain_settings.active_domains]
+
+ if "Healthcare" in active_domains:
+ manage_invoice_submit_cancel(self, "on_cancel")
+
def update_status_updater_args(self):
if cint(self.update_stock):
self.status_updater.extend([{
@@ -719,7 +735,8 @@
"debit_in_account_currency": grand_total_in_company_currency \
if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
- "against_voucher_type": self.doctype
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center
}, self.party_account_currency)
)
@@ -780,13 +797,14 @@
"against": "Expense account - " + cstr(self.loyalty_redemption_account) + " for the Loyalty Program",
"credit": self.loyalty_amount,
"against_voucher": self.return_against if cint(self.is_return) else self.name,
- "against_voucher_type": self.doctype
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center
})
)
gl_entries.append(
self.get_gl_dict({
"account": self.loyalty_redemption_account,
- "cost_center": self.loyalty_redemption_cost_center,
+ "cost_center": self.cost_center or self.loyalty_redemption_cost_center,
"against": self.customer,
"debit": self.loyalty_amount,
"remark": "Loyalty Points redeemed by the customer"
@@ -810,6 +828,7 @@
else payment_mode.amount,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype,
+ "cost_center": self.cost_center
}, self.party_account_currency)
)
@@ -821,7 +840,8 @@
"debit": payment_mode.base_amount,
"debit_in_account_currency": payment_mode.base_amount \
if payment_mode_account_currency==self.company_currency \
- else payment_mode.amount
+ else payment_mode.amount,
+ "cost_center": self.cost_center
}, payment_mode_account_currency)
)
@@ -838,7 +858,8 @@
"debit_in_account_currency": flt(self.base_change_amount) \
if self.party_account_currency==self.company_currency else flt(self.change_amount),
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
- "against_voucher_type": self.doctype
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center
}, self.party_account_currency)
)
@@ -846,7 +867,8 @@
self.get_gl_dict({
"account": self.account_for_change_amount,
"against": self.customer,
- "credit": self.base_change_amount
+ "credit": self.base_change_amount,
+ "cost_center": self.cost_center
})
)
else:
@@ -868,7 +890,8 @@
"credit_in_account_currency": self.base_write_off_amount \
if self.party_account_currency==self.company_currency else self.write_off_amount,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
- "against_voucher_type": self.doctype
+ "against_voucher_type": self.doctype,
+ "cost_center": self.cost_center
}, self.party_account_currency)
)
gl_entries.append(
@@ -878,7 +901,7 @@
"debit": self.base_write_off_amount,
"debit_in_account_currency": self.base_write_off_amount \
if write_off_account_currency==self.company_currency else self.write_off_amount,
- "cost_center": self.write_off_cost_center or default_cost_center
+ "cost_center": self.cost_center or self.write_off_cost_center or default_cost_center
}, write_off_account_currency)
)
@@ -893,7 +916,7 @@
"against": self.customer,
"credit_in_account_currency": self.base_rounding_adjustment,
"credit": self.base_rounding_adjustment,
- "cost_center": round_off_cost_center,
+ "cost_center": self.cost_center or round_off_cost_center,
}
))
@@ -1178,6 +1201,43 @@
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), merge_entries=True)
+ # Healthcare
+ def set_healthcare_services(self, checked_values):
+ self.set("items", [])
+ from erpnext.stock.get_item_details import get_item_details
+ for checked_item in checked_values:
+ item_line = self.append("items", {})
+ price_list, price_list_currency = frappe.db.get_values("Price List", {"selling": 1}, ['name', 'currency'])[0]
+ args = {
+ 'doctype': "Sales Invoice",
+ 'item_code': checked_item['item'],
+ 'company': self.company,
+ 'customer': frappe.db.get_value("Patient", self.patient, "customer"),
+ 'selling_price_list': price_list,
+ 'price_list_currency': price_list_currency,
+ 'plc_conversion_rate': 1.0,
+ 'conversion_rate': 1.0
+ }
+ item_details = get_item_details(args)
+ item_line.item_code = checked_item['item']
+ item_line.qty = 1
+ if checked_item['qty']:
+ item_line.qty = checked_item['qty']
+ if checked_item['rate']:
+ item_line.rate = checked_item['rate']
+ else:
+ item_line.rate = item_details.price_list_rate
+ item_line.amount = float(item_line.rate) * float(item_line.qty)
+ if checked_item['income_account']:
+ item_line.income_account = checked_item['income_account']
+ if checked_item['dt']:
+ item_line.reference_dt = checked_item['dt']
+ if checked_item['dn']:
+ item_line.reference_dn = checked_item['dn']
+ if checked_item['description']:
+ item_line.description = checked_item['description']
+
+ self.set_missing_values(for_validate = True)
def booked_deferred_revenue(start_date=None, end_date=None):
# check for the sales invoice for which GL entries has to be done
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 468ed9f..b0fbc98 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1438,6 +1438,66 @@
si_doc = frappe.get_doc('Sales Invoice', si.name)
self.assertEqual(si_doc.outstanding_amount, 0)
+ def test_sales_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
+ accounts_settings.save()
+ cost_center = "_Test Cost Center for BS Account - _TC"
+ create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
+
+ si = create_sales_invoice_against_cost_center(cost_center=cost_center, debit_to="Debtors - _TC")
+ self.assertEqual(si.cost_center, cost_center)
+
+ expected_values = {
+ "Debtors - _TC": {
+ "cost_center": cost_center
+ },
+ "Sales - _TC": {
+ "cost_center": cost_center
+ }
+ }
+
+ gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
+ order by account asc""", si.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+
+ def test_sales_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
+ accounts_settings.save()
+ cost_center = "_Test Cost Center - _TC"
+ si = create_sales_invoice(debit_to="Debtors - _TC")
+
+ expected_values = {
+ "Debtors - _TC": {
+ "cost_center": None
+ },
+ "Sales - _TC": {
+ "cost_center": cost_center
+ }
+ }
+
+ gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
+ order by account asc""", si.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+ for gle in gl_entries:
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
args = frappe._dict(args)
@@ -1478,6 +1538,48 @@
return si
+def create_sales_invoice_against_cost_center(**args):
+ si = frappe.new_doc("Sales Invoice")
+ args = frappe._dict(args)
+ if args.posting_date:
+ si.set_posting_time = 1
+ si.posting_date = args.posting_date or nowdate()
+
+ si.company = args.company or "_Test Company"
+ si.cost_center = args.cost_center or "_Test Cost Center - _TC"
+ si.customer = args.customer or "_Test Customer"
+ si.debit_to = args.debit_to or "Debtors - _TC"
+ si.update_stock = args.update_stock
+ si.is_pos = args.is_pos
+ si.is_return = args.is_return
+ si.return_against = args.return_against
+ si.currency=args.currency or "INR"
+ si.conversion_rate = args.conversion_rate or 1
+
+ si.append("items", {
+ "item_code": args.item or args.item_code or "_Test Item",
+ "gst_hsn_code": "999800",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty or 1,
+ "rate": args.rate or 100,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
+ "serial_no": args.serial_no
+ })
+
+ if not args.do_not_save:
+ si.insert()
+ if not args.do_not_submit:
+ si.submit()
+ else:
+ si.payment_schedule = []
+ else:
+ si.payment_schedule = []
+
+ return si
+
+
test_dependencies = ["Journal Entry", "Contact", "Address"]
test_records = frappe.get_test_records('Sales Invoice')
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 0dc4ecf..9a2c095 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -102,17 +102,25 @@
return tds_amount
-def get_advance_vouchers(supplier, fiscal_year):
+def get_advance_vouchers(supplier, fiscal_year=None, company=None, from_date=None, to_date=None):
+ condition = "fiscal_year=%s" % fiscal_year
+ if from_date and to_date:
+ condition = "company=%s and posting_date between %s and %s" % (company, from_date, to_date)
+
return frappe.db.sql_list("""
select distinct voucher_no
from `tabGL Entry`
- where party=%s and fiscal_year=%s and debit > 0
- """, (supplier, fiscal_year))
+ where party=%s and %s and debit > 0
+ """, (supplier, condition))
-def get_debit_note_amount(supplier, year_start_date, year_end_date):
+def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None):
+ condition = ""
+ if company:
+ condition = " and company=%s " % company
+
return flt(frappe.db.sql("""
select abs(sum(net_total))
from `tabPurchase Invoice`
- where supplier=%s and is_return=1 and docstatus=1
+ where supplier=%s %s and is_return=1 and docstatus=1
and posting_date between %s and %s
- """, (supplier, year_start_date, year_end_date)))
\ No newline at end of file
+ """, (supplier, condition, year_start_date, year_end_date)))
diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js
index ce1f34b..528e3d3 100755
--- a/erpnext/accounts/page/pos/pos.js
+++ b/erpnext/accounts/page/pos/pos.js
@@ -123,6 +123,10 @@
me.sync_sales_invoice()
});
+ this.page.add_menu_item(__("Cashier Closing"), function () {
+ frappe.set_route('List', 'Cashier Closing');
+ });
+
this.page.add_menu_item(__("POS Profile"), function () {
frappe.set_route('List', 'POS Profile');
});
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.json b/erpnext/accounts/report/balance_sheet/balance_sheet.json
index 4e75344..f67a34b 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.json
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.json
@@ -1,17 +1,17 @@
{
"add_total_row": 0,
- "apply_user_permissions": 1,
"creation": "2014-07-14 05:24:20.385279",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 2,
"is_standard": "Yes",
- "modified": "2017-02-24 20:12:47.161127",
+ "modified": "2018-09-07 12:18:21.850851",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Balance Sheet",
"owner": "Administrator",
+ "prepared_report": 0,
"ref_doctype": "GL Entry",
"report_name": "Balance Sheet",
"report_type": "Script Report",
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 3a97f44..74ca258 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -351,7 +351,9 @@
"from_date": from_date,
"to_date": to_date,
"lft": root_lft,
- "rgt": root_rgt
+ "rgt": root_rgt,
+ "cost_center": filters.cost_center,
+ "project": filters.project
},
as_dict=True)
@@ -378,13 +380,11 @@
if not isinstance(filters.get("project"), list):
projects = str(filters.get("project")).strip()
filters.project = [d.strip() for d in projects.split(',') if d]
- additional_conditions.append("project = '%s'" % (frappe.db.escape(filters.get("project"))))
+ additional_conditions.append("project in %(project)s")
if filters.get("cost_center"):
- if not isinstance(filters.get("cost_center"), list):
- cost_centers = str(filters.get("cost_center")).strip()
- filters.cost_center = [d.strip() for d in cost_centers.split(',') if d]
- additional_conditions.append(get_cost_center_cond(filters.get("cost_center")))
+ filters.cost_center = get_cost_centers_with_children(filters.cost_center)
+ additional_conditions.append("cost_center in %(cost_center)s")
company_finance_book = erpnext.get_default_finance_book(filters.get("company"))
@@ -397,14 +397,17 @@
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
+def get_cost_centers_with_children(cost_centers):
+ if not isinstance(cost_centers, list):
+ cost_centers = [d.strip() for d in str(cost_centers).strip().split(',') if d]
-def get_cost_center_cond(cost_center):
- cost_centers = frappe.db.get_all("Cost Center", {"name": ["in", cost_center]},
- ["name", "lft", "rgt"])
+ all_cost_centers = []
+ for d in cost_centers:
+ lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"])
+ children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
+ all_cost_centers += [c.name for c in children]
- lft_rgt = " or ".join(["(lft >=%s and rgt <=%s)" % (d.lft, d.rgt) for d in cost_centers])
-
- return """ cost_center in (select name from `tabCost Center` where %s)""" % (lft_rgt)
+ return list(set(all_cost_centers))
def get_columns(periodicity, period_list, accumulated_values=1, company=None):
columns = [{
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 2d174ff..56663d3 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -8,7 +8,7 @@
from frappe.utils import getdate, cstr, flt, fmt_money
from frappe import _, _dict
from erpnext.accounts.utils import get_account_currency
-
+from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
from six import iteritems
def execute(filters=None):
@@ -154,6 +154,10 @@
conditions.append("""account in (select name from tabAccount
where lft>=%s and rgt<=%s and docstatus<2)""" % (lft, rgt))
+ if filters.get("cost_center"):
+ filters.cost_center = get_cost_centers_with_children(filters.cost_center)
+ conditions.append("cost_center in %(cost_center)s")
+
if filters.get("voucher_no"):
conditions.append("voucher_no=%(voucher_no)s")
@@ -174,9 +178,6 @@
if filters.get("project"):
conditions.append("project in %(project)s")
- if filters.get("cost_center"):
- conditions.append("cost_center in %(cost_center)s")
-
company_finance_book = erpnext.get_default_finance_book(filters.get("company"))
if not filters.get("finance_book") or (filters.get("finance_book") == company_finance_book):
filters['finance_book'] = company_finance_book
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
index 1804733..250e516 100644
--- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
+++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
@@ -8,37 +8,6 @@
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
{
- "fieldname":"cost_center",
- "label": __("Cost Center"),
- "fieldtype": "MultiSelect",
- get_data: function() {
- var cost_centers = frappe.query_report.get_filter_value("cost_center") || "";
-
- const values = cost_centers.split(/\s*,\s*/).filter(d => d);
- const txt = cost_centers.match(/[^,\s*]*$/)[0] || '';
- let data = [];
-
- frappe.call({
- type: "GET",
- method:'frappe.desk.search.search_link',
- async: false,
- no_spinner: true,
- args: {
- doctype: "Cost Center",
- txt: txt,
- filters: {
- "company": frappe.query_report.get_filter_value("company"),
- "name": ["not in", values]
- }
- },
- callback: function(r) {
- data = r.results;
- }
- });
- return data;
- }
- },
- {
"fieldname":"project",
"label": __("Project"),
"fieldtype": "MultiSelect",
diff --git a/erpnext/accounts/report/tds_computation_summary/__init__.py b/erpnext/accounts/report/tds_computation_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/tds_computation_summary/__init__.py
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js
new file mode 100644
index 0000000..74669c4
--- /dev/null
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js
@@ -0,0 +1,43 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["TDS Computation Summary"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "default": frappe.defaults.get_default('company')
+ },
+ {
+ "fieldname":"supplier",
+ "label": __("Supplier"),
+ "fieldtype": "Link",
+ "options": "Supplier",
+ "get_query": function() {
+ return {
+ "filters": {
+ "tax_withholding_category": ["!=",""],
+ }
+ }
+ }
+ },
+ {
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ "reqd": 1,
+ "width": "60px"
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.get_today(),
+ "reqd": 1,
+ "width": "60px"
+ }
+ ]
+}
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json
new file mode 100644
index 0000000..6082ed2
--- /dev/null
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json
@@ -0,0 +1,33 @@
+{
+ "add_total_row": 0,
+ "creation": "2018-08-21 11:25:00.551823",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2018-08-21 11:25:00.551823",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "TDS Computation Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Purchase Invoice",
+ "report_name": "TDS Computation Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Auditor"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
new file mode 100644
index 0000000..391287b
--- /dev/null
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
@@ -0,0 +1,135 @@
+import frappe
+from frappe import _
+from frappe.utils import flt
+from erpnext.accounts.utils import get_fiscal_year
+from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category \
+ import get_advance_vouchers, get_debit_note_amount
+
+def execute(filters=None):
+ validate_filters(filters)
+
+ columns = get_columns()
+ res = get_result(filters)
+
+ return columns, res
+
+def validate_filters(filters):
+ ''' Validate if dates are properly set and lie in the same fiscal year'''
+ if filters.from_date > filters.to_date:
+ frappe.throw(_("From Date must be before To Date"))
+
+ from_year = get_fiscal_year(filters.from_date)[0]
+ to_year = get_fiscal_year(filters.to_date)[0]
+ if from_year != to_year:
+ frappe.throw(_("From Date and To Date lie in different Fiscal Year"))
+
+ filters["fiscal_year"] = from_year
+
+def get_result(filters):
+ # if no supplier selected, fetch data for all tds applicable supplier
+ # else fetch relevant data for selected supplier
+ pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
+ fields = ["name", pan+" as pan", "tax_withholding_category", "supplier_type"]
+ if filters.supplier:
+ filters.supplier = frappe.db.get_list('Supplier',
+ {"name": filters.supplier}, fields)
+ else:
+ filters.supplier = frappe.db.get_list('Supplier',
+ {"tax_withholding_category": ["!=", ""]}, fields)
+
+ out = []
+ for supplier in filters.supplier:
+ tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category)
+ rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year][0]
+ account = [d.account for d in tds.accounts if d.company == filters.company][0]
+
+ total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account,
+ filters.company, filters.from_date, filters.to_date)
+
+ if total_invoiced_amount or tds_deducted:
+ out.append([supplier.pan, supplier.name, tds.name, supplier.supplier_type,
+ rate, total_invoiced_amount, tds_deducted])
+
+ return out
+
+def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date):
+ ''' calculate total invoice amount and total tds deducted for given supplier '''
+
+ entries = frappe.db.sql("""
+ select voucher_no, credit
+ from `tabGL Entry`
+ where party in (%s) and credit > 0
+ and company=%s and posting_date between %s and %s
+ """, (supplier, company, from_date, to_date), as_dict=1)
+
+ supplier_credit_amount = flt(sum([d.credit for d in entries]))
+
+ vouchers = [d.voucher_no for d in entries]
+ vouchers += get_advance_vouchers(supplier, company=company,
+ from_date=from_date, to_date=to_date)
+
+ tds_deducted = 0
+ if vouchers:
+ tds_deducted = flt(frappe.db.sql("""
+ select sum(credit)
+ from `tabGL Entry`
+ where account=%s and posting_date between %s and %s
+ and company=%s and credit > 0 and voucher_no in ({0})
+ """.format(', '.join(["'%s'" % d for d in vouchers])),
+ (account, from_date, to_date, company))[0][0])
+
+ debit_note_amount = get_debit_note_amount(supplier, from_date, to_date, company=company)
+
+ total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount
+
+ return total_invoiced_amount, tds_deducted
+
+def get_columns():
+ columns = [
+ {
+ "label": _("PAN"),
+ "fieldname": "pan",
+ "fieldtype": "Data",
+ "width": 90
+ },
+ {
+ "label": _("Supplier"),
+ "options": "Supplier",
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "width": 180
+ },
+ {
+ "label": _("Section Code"),
+ "options": "Tax Withholding Category",
+ "fieldname": "section_code",
+ "fieldtype": "Link",
+ "width": 180
+ },
+ {
+ "label": _("Entity Type"),
+ "fieldname": "entity_type",
+ "fieldtype": "Data",
+ "width": 180
+ },
+ {
+ "label": _("TDS Rate %"),
+ "fieldname": "tds_rate",
+ "fieldtype": "Float",
+ "width": 90
+ },
+ {
+ "label": _("Total Amount Credited"),
+ "fieldname": "total_amount_credited",
+ "fieldtype": "Float",
+ "width": 90
+ },
+ {
+ "label": _("Amount of TDS Deducted"),
+ "fieldname": "tds_deducted",
+ "fieldtype": "Float",
+ "width": 90
+ }
+ ]
+
+ return columns
diff --git a/erpnext/accounts/report/tds_payable_monthly/__init__.py b/erpnext/accounts/report/tds_payable_monthly/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/tds_payable_monthly/__init__.py
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js
new file mode 100644
index 0000000..232d053
--- /dev/null
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js
@@ -0,0 +1,89 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["TDS Payable Monthly"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "default": frappe.defaults.get_default('company')
+ },
+ {
+ "fieldname":"supplier",
+ "label": __("Supplier"),
+ "fieldtype": "Link",
+ "options": "Supplier",
+ "get_query": function() {
+ return {
+ "filters": {
+ "tax_withholding_category": ["!=", ""],
+ }
+ }
+ },
+ on_change: function() {
+ frappe.query_report.set_filter_value("purchase_invoice", "");
+ frappe.query_report.refresh();
+ }
+ },
+ {
+ "fieldname":"purchase_invoice",
+ "label": __("Purchase Invoice"),
+ "fieldtype": "Link",
+ "options": "Purchase Invoice",
+ "get_query": function() {
+ return {
+ "filters": {
+ "name": ["in", frappe.query_report.invoices]
+ }
+ }
+ },
+ on_change: function() {
+ let supplier = frappe.query_report.get_filter_value('supplier');
+ if(!supplier) return; // return if no supplier selected
+
+ // filter invoices based on selected supplier
+ let invoices = [];
+ frappe.query_report.invoice_data.map(d => {
+ if(d.supplier==supplier)
+ invoices.push(d.name)
+ });
+ frappe.query_report.invoices = invoices;
+ frappe.query_report.refresh();
+ }
+ },
+ {
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ "reqd": 1,
+ "width": "60px"
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.get_today(),
+ "reqd": 1,
+ "width": "60px"
+ }
+ ],
+
+ onload: function(report) {
+ // fetch all tds applied invoices
+ frappe.call({
+ "method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices",
+ callback: function(r) {
+ let invoices = [];
+ r.message.map(d => {
+ invoices.push(d.name);
+ });
+
+ report["invoice_data"] = r.message;
+ report["invoices"] = invoices;
+ }
+ });
+ }
+}
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json
new file mode 100644
index 0000000..6a83272
--- /dev/null
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json
@@ -0,0 +1,33 @@
+{
+ "add_total_row": 0,
+ "creation": "2018-08-21 11:32:30.874923",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2018-08-21 11:33:40.804532",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "TDS Payable Monthly",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Purchase Invoice",
+ "report_name": "TDS Payable Monthly",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Auditor"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
new file mode 100644
index 0000000..0e6f0a2
--- /dev/null
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -0,0 +1,190 @@
+# 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 getdate
+
+def execute(filters=None):
+ filters["invoices"] = frappe.cache().hget("invoices", frappe.session.user)
+ validate_filters(filters)
+ set_filters(filters)
+
+ columns = get_columns()
+ res = get_result(filters)
+
+ return columns, res
+
+def validate_filters(filters):
+ ''' Validate if dates are properly set '''
+ if filters.from_date > filters.to_date:
+ frappe.throw(_("From Date must be before To Date"))
+
+def set_filters(filters):
+ invoices = []
+
+ if not filters["invoices"]:
+ filters["invoices"] = get_tds_invoices()
+ if filters.supplier and filters.purchase_invoice:
+ for d in filters["invoices"]:
+ if d.name == filters.purchase_invoice and d.supplier == filters.supplier:
+ invoices.append(d)
+ elif filters.supplier and not filters.purchase_invoice:
+ for d in filters["invoices"]:
+ if d.supplier == filters.supplier:
+ invoices.append(d)
+ elif filters.purchase_invoice and not filters.supplier:
+ for d in filters["invoices"]:
+ if d.name == filters.purchase_invoice:
+ invoices.append(d)
+
+ filters["invoices"] = invoices if invoices else filters["invoices"]
+
+def get_result(filters):
+ supplier_map, tds_docs = get_supplier_map(filters)
+ gle_map = get_gle_map(filters)
+
+ out = []
+ for d in gle_map:
+ tds_deducted, total_amount_credited = 0, 0
+ supplier = supplier_map[d]
+
+ tds_doc = tds_docs[supplier.tax_withholding_category]
+ account = [i.account for i in tds_doc.accounts if i.company == filters.company][0]
+
+ for k in gle_map[d]:
+ if k.party == supplier_map[d] and k.credit > 0:
+ total_amount_credited += k.credit
+ elif k.account == account and k.credit > 0:
+ tds_deducted = k.credit
+ total_amount_credited += k.credit
+
+ rate = [i.tax_withholding_rate for i in tds_doc.rates
+ if i.fiscal_year == gle_map[d][0].fiscal_year][0]
+
+ if getdate(filters.from_date) <= gle_map[d][0].posting_date \
+ and getdate(filters.to_date) >= gle_map[d][0].posting_date:
+ out.append([supplier.pan, supplier.name, tds_doc.name,
+ supplier.supplier_type, rate, total_amount_credited, tds_deducted,
+ gle_map[d][0].posting_date, "Purchase Invoice", d])
+
+ return out
+
+def get_supplier_map(filters):
+ # create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}}
+ # pre-fetch all distinct applicable tds docs
+ supplier_map, tds_docs = {}, {}
+ pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
+ supplier_detail = frappe.db.get_all('Supplier',
+ {"name": ["in", [d.supplier for d in filters["invoices"]]]},
+ ["tax_withholding_category", "name", pan+" as pan", "supplier_type"])
+
+ for d in filters["invoices"]:
+ supplier_map[d.get("name")] = [k for k in supplier_detail
+ if k.name == d.get("supplier")][0]
+
+ for d in supplier_detail:
+ if d.get("tax_withholding_category") not in tds_docs:
+ tds_docs[d.get("tax_withholding_category")] = \
+ frappe.get_doc("Tax Withholding Category", d.get("tax_withholding_category"))
+
+ return supplier_map, tds_docs
+
+def get_gle_map(filters):
+ # create gle_map of the form
+ # {"purchase_invoice": list of dict of all gle created for this invoice}
+ gle_map = {}
+ gle = frappe.db.get_all('GL Entry',\
+ {"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]]},
+ ["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"])
+
+ for d in gle:
+ if not d.voucher_no in gle_map:
+ gle_map[d.voucher_no] = [d]
+ else:
+ gle_map[d.voucher_no].append(d)
+
+ return gle_map
+
+def get_columns():
+ pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
+ columns = [
+ {
+ "label": _(frappe.unscrub(pan)),
+ "fieldname": pan,
+ "fieldtype": "Data",
+ "width": 90
+ },
+ {
+ "label": _("Supplier"),
+ "options": "Supplier",
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "width": 180
+ },
+ {
+ "label": _("Section Code"),
+ "options": "Tax Withholding Category",
+ "fieldname": "section_code",
+ "fieldtype": "Link",
+ "width": 180
+ },
+ {
+ "label": _("Entity Type"),
+ "fieldname": "entity_type",
+ "fieldtype": "Data",
+ "width": 180
+ },
+ {
+ "label": _("TDS Rate %"),
+ "fieldname": "tds_rate",
+ "fieldtype": "Float",
+ "width": 90
+ },
+ {
+ "label": _("Total Amount Credited"),
+ "fieldname": "total_amount_credited",
+ "fieldtype": "Float",
+ "width": 90
+ },
+ {
+ "label": _("Amount of TDS Deducted"),
+ "fieldname": "tds_deducted",
+ "fieldtype": "Float",
+ "width": 90
+ },
+ {
+ "label": _("Date of Transaction"),
+ "fieldname": "transaction_date",
+ "fieldtype": "Date",
+ "width": 90
+ },
+ {
+ "label": _("Transaction Type"),
+ "fieldname": "transaction_type",
+ "width": 90
+ },
+ {
+ "label": _("Reference No."),
+ "fieldname": "ref_no",
+ "fieldtype": "Dynamic Link",
+ "options": "transaction_type",
+ "width": 90
+ }
+ ]
+
+ return columns
+
+@frappe.whitelist()
+def get_tds_invoices():
+ # fetch tds applicable supplier and fetch invoices for these suppliers
+ suppliers = [d.name for d in frappe.db.get_list("Supplier",
+ {"tax_withholding_category": ["!=", ""]}, ["name"])]
+
+ invoices = frappe.db.get_list("Purchase Invoice",
+ {"supplier": ["in", suppliers]}, ["name", "supplier"])
+
+ frappe.cache().hset("invoices", frappe.session.user, invoices)
+
+ return invoices
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js
index 8e95d8c..c09fa71 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.js
+++ b/erpnext/accounts/report/trial_balance/trial_balance.js
@@ -13,6 +13,21 @@
"reqd": 1
},
{
+ "fieldname":"cost_center",
+ "label": __("Cost Center"),
+ "fieldtype": "Link",
+ "options": "Cost Center",
+ "get_query": function() {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ "doctype": "Cost Center",
+ "filters": {
+ "company": company,
+ }
+ }
+ }
+ },
+ {
"fieldname": "fiscal_year",
"label": __("Fiscal Year"),
"fieldtype": "Link",
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index ae128a7..6fbe97d 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -84,7 +84,7 @@
throw(_("{0} '{1}' not in Fiscal Year {2}").format(label, formatdate(date), fiscal_year))
@frappe.whitelist()
-def get_balance_on(account=None, date=None, party_type=None, party=None, company=None, in_account_currency=True):
+def get_balance_on(account=None, date=None, party_type=None, party=None, company=None, in_account_currency=True, cost_center=None):
if not account and frappe.form_dict.get("account"):
account = frappe.form_dict.get("account")
if not date and frappe.form_dict.get("date"):
@@ -93,6 +93,9 @@
party_type = frappe.form_dict.get("party_type")
if not party and frappe.form_dict.get("party"):
party = frappe.form_dict.get("party")
+ if not cost_center and frappe.form_dict.get("cost_center"):
+ cost_center = frappe.form_dict.get("cost_center")
+
cond = []
if date:
@@ -113,17 +116,36 @@
# hence, assuming balance as 0.0
return 0.0
+ allow_cost_center_in_entry_of_bs_account = get_allow_cost_center_in_entry_of_bs_account()
+
+ if cost_center and allow_cost_center_in_entry_of_bs_account:
+ cc = frappe.get_doc("Cost Center", cost_center)
+ if cc.is_group:
+ cond.append(""" exists (
+ select 1 from `tabCost Center` cc where cc.name = gle.cost_center
+ and cc.lft >= %s and cc.rgt <= %s
+ )""" % (cc.lft, cc.rgt))
+
+ else:
+ cond.append("""gle.cost_center = "%s" """ % (frappe.db.escape(cost_center, percent=False), ))
+
+
if account:
+
acc = frappe.get_doc("Account", account)
if not frappe.flags.ignore_account_permission:
acc.check_permission("read")
- # for pl accounts, get balance within a fiscal year
- if acc.report_type == 'Profit and Loss':
+
+ if not allow_cost_center_in_entry_of_bs_account and acc.report_type == 'Profit and Loss':
+ # for pl accounts, get balance within a fiscal year
cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
% year_start_date)
-
+ elif allow_cost_center_in_entry_of_bs_account:
+ # for all accounts, get balance within a fiscal year if maintain cost center in balance account is checked
+ cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
+ % year_start_date)
# different filter for group and ledger - improved performance
if acc.is_group:
cond.append("""exists (
@@ -830,3 +852,10 @@
accounts = [d for d in accounts if d['parent_account']==parent]
return accounts
+
+def get_allow_cost_center_in_entry_of_bs_account():
+ def generator():
+ return cint(frappe.db.get_value('Accounts Settings', None, 'allow_cost_center_in_entry_of_bs_account'))
+ return frappe.local_cache("get_allow_cost_center_in_entry_of_bs_account", (), generator, regenerate_if_none=True)
+
+
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 0423588..7625416 100755
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -2341,7 +2341,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-08-06 05:16:58.258276",
+ "modified": "2018-09-07 05:16:58.258276",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index 0cadc34..5b095b0 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -384,6 +384,72 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "default": "Company",
+ "fieldname": "supplier_type",
+ "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": "Supplier Type",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Company\nIndividual",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "pan",
+ "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": "PAN",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "language",
"fieldtype": "Link",
"hidden": 0,
@@ -1364,7 +1430,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-29 06:25:52.313864",
+ "modified": "2018-09-07 08:48:57.719713",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py
index 5526ac8..660f78c 100644
--- a/erpnext/config/accounts.py
+++ b/erpnext/config/accounts.py
@@ -34,6 +34,11 @@
},
{
"type": "doctype",
+ "name": "Cashier Closing",
+ "description": _("Cashier Closing")
+ },
+ {
+ "type": "doctype",
"name": "Auto Repeat",
"label": _("Auto Repeat"),
"description": _("To make recurring documents")
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index b13e404..d94564e 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -129,7 +129,7 @@
self.status = s[0]
break
elif s[1].startswith("eval:"):
- if frappe.safe_eval(s[1][5:], None, { "self": self.as_dict(), "getdate": getdate,
+ if frappe.safe_eval(s[1][5:], None, { "self": self.as_dict(), "getdate": getdate,
"nowdate": nowdate, "get_value": frappe.db.get_value }):
self.status = s[0]
break
@@ -256,7 +256,7 @@
if args['detail_id']:
if not args.get("extra_cond"): args["extra_cond"] = ""
-
+
frappe.db.sql("""update `tab%(target_dt)s`
set %(target_field)s = (
(select ifnull(sum(%(source_field)s), 0)
@@ -281,7 +281,7 @@
"""Update percent field in parent transaction"""
self._update_modified(args, update_modified)
-
+
if args.get('target_parent_field'):
frappe.db.sql("""update `tab%(target_parent_dt)s`
set %(target_parent_field)s = round(
@@ -335,8 +335,7 @@
from `tab%s Item` where %s=%s and docstatus=1""" %
(self.doctype, ref_fieldname, '%s'), (ref_dn))[0][0])
- per_billed = ((ref_doc_qty if billed_qty > ref_doc_qty else billed_qty)\
- / ref_doc_qty)*100
+ per_billed = (min(ref_doc_qty, billed_qty) / ref_doc_qty) * 100
ref_doc = frappe.get_doc(ref_dt, ref_dn)
diff --git a/erpnext/domains/healthcare.py b/erpnext/domains/healthcare.py
index 5a54cf6..ee8dc81 100644
--- a/erpnext/domains/healthcare.py
+++ b/erpnext/domains/healthcare.py
@@ -21,9 +21,30 @@
'Patient'
],
'custom_fields': {
- 'Sales Invoice': dict(fieldname='appointment', label='Patient Appointment',
- fieldtype='Link', options='Patient Appointment',
- insert_after='customer')
+ 'Sales Invoice': [
+ {
+ 'fieldname': 'patient', 'label': 'Patient', 'fieldtype': 'Link', 'options': 'Patient',
+ 'insert_after': 'naming_series'
+ },
+ {
+ 'fieldname': 'patient_name', 'label': 'Patient Name', 'fieldtype': 'Data', 'fetch_from': 'patient.patient_name',
+ 'insert_after': 'patient', 'read_only': True
+ },
+ {
+ 'fieldname': 'ref_practitioner', 'label': 'Referring Practitioner', 'fieldtype': 'Link', 'options': 'Healthcare Practitioner',
+ 'insert_after': 'customer'
+ }
+ ],
+ 'Sales Invoice Item': [
+ {
+ 'fieldname': 'reference_dt', 'label': 'Reference DocType', 'fieldtype': 'Link', 'options': 'DocType',
+ 'insert_after': 'edit_references'
+ },
+ {
+ 'fieldname': 'reference_dn', 'label': 'Reference Name', 'fieldtype': 'Dynamic Link', 'options': 'reference_dt',
+ 'insert_after': 'reference_dt'
+ }
+ ]
},
'on_setup': 'erpnext.healthcare.setup.setup_healthcare'
}
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
index cb11ece..3234e7a 100644
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
@@ -3,8 +3,12 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe, time, dateutil, math, csv, StringIO
-import amazon_mws_api as mws
+import frappe, time, dateutil, math, csv
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+import erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_api as mws
from frappe import _
#Get and Create Products
@@ -22,7 +26,7 @@
listings_response = reports.get_report(report_id=report_id)
#Get ASIN Codes
- string_io = StringIO.StringIO(listings_response.original)
+ string_io = StringIO(listings_response.original)
csv_rows = list(csv.reader(string_io, delimiter=str('\t')))
asin_list = list(set([row[1] for row in csv_rows[1:]]))
#break into chunks of 10
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
index 798e637..bf6d85b 100755
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
@@ -9,7 +9,7 @@
import hashlib
import hmac
import base64
-import xml_utils
+from erpnext.erpnext_integrations.doctype.amazon_mws_settings import xml_utils
import re
try:
from xml.etree.ElementTree import ParseError as XMLError
@@ -196,7 +196,7 @@
except XMLError:
parsed_response = DataWrapper(data, response.headers)
- except HTTPError, e:
+ except HTTPError as e:
error = MWSError(str(e))
error.response = e.response
raise error
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.json b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.json
index 771d1f2..607ca4f 100644
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.json
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.json
@@ -864,7 +864,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "default": "1",
+ "default": "",
"description": "Check this to enable a scheduled Daily synchronization routine via scheduler",
"fieldname": "enable_synch",
"fieldtype": "Check",
@@ -935,7 +935,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-23 20:52:58.471424",
+ "modified": "2018-09-07 16:45:44.439834",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Amazon MWS Settings",
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
index 7e64915..249a73f 100644
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
+++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
@@ -7,12 +7,15 @@
from frappe.model.document import Document
import dateutil
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
-from amazon_methods import get_products_details, get_orders
+from erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods import get_products_details, get_orders
class AmazonMWSSettings(Document):
def validate(self):
if self.enable_amazon == 1:
+ self.enable_synch = 1
setup_custom_fields()
+ else:
+ self.enable_synch = 0
def get_products_details(self):
if self.enable_amazon == 1:
@@ -25,7 +28,7 @@
def schedule_get_order_details():
mws_settings = frappe.get_doc("Amazon MWS Settings")
- if mws_settings.enable_synch:
+ if mws_settings.enable_synch and mws_settings.enable_amazon:
after_date = dateutil.parser.parse(mws_settings.after_date).strftime("%Y-%m-%d")
get_orders(after_date = after_date)
diff --git a/erpnext/healthcare/doctype/appointment_type/appointment_type.json b/erpnext/healthcare/doctype/appointment_type/appointment_type.json
index 4dc40b1..ceabce2 100644
--- a/erpnext/healthcare/doctype/appointment_type/appointment_type.json
+++ b/erpnext/healthcare/doctype/appointment_type/appointment_type.json
@@ -1,7 +1,7 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
- "allow_import": 0,
+ "allow_import": 1,
"allow_rename": 1,
"autoname": "field:appointment_type",
"beta": 1,
@@ -152,7 +152,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-06-13 00:04:24.597019",
+ "modified": "2018-08-08 12:57:54.544216",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Appointment Type",
@@ -208,5 +208,6 @@
"sort_order": "DESC",
"title_field": "",
"track_changes": 0,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
index 9fc5b37..7f866e1 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
@@ -264,21 +264,20 @@
let args = null;
if(d.item_code) {
args = {
- 'item_code' : d.item_code,
- 'transfer_qty' : d.transfer_qty,
- 'company' : frm.doc.company,
- 'quantity' : d.qty
+ 'doctype' : "Clinical Procedure",
+ 'item_code' : d.item_code,
+ 'company' : frm.doc.company,
+ 'warehouse': frm.doc.warehouse
};
return frappe.call({
- doc: frm.doc,
- method: "get_item_details",
- args: args,
+ method: "erpnext.stock.get_item_details.get_item_details",
+ args: {args: args},
callback: function(r) {
if(r.message) {
- var d = locals[cdt][cdn];
- $.each(r.message, function(k, v){
- d[k] = v;
- });
+ frappe.model.set_value(cdt, cdn, "item_name", r.message.item_name);
+ frappe.model.set_value(cdt, cdn, "stock_uom", r.message.stock_uom);
+ frappe.model.set_value(cdt, cdn, "conversion_factor", r.message.conversion_factor);
+ frappe.model.set_value(cdt, cdn, "actual_qty", r.message.actual_qty);
refresh_field("items");
}
}
diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json
index 04b96e9..c755b7f 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json
@@ -252,6 +252,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "prescription",
+ "fieldtype": "Link",
+ "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": "Procedure Prescription",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Procedure Prescription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "medical_department",
"fieldtype": "Link",
"hidden": 0,
@@ -480,7 +513,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "is_invoiced",
+ "default": "0",
+ "fieldname": "invoiced",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -489,9 +523,9 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Is Invoiced",
+ "label": "Invoiced",
"length": 0,
- "no_copy": 0,
+ "no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -677,6 +711,139 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "default": "0",
+ "fieldname": "invoice_separately_as_consumables",
+ "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": "Consumables Invoice Separately",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "invoice_separately_as_consumables",
+ "fieldname": "consumable_total_amount",
+ "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": "Consumable Total Amount",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "invoice_separately_as_consumables",
+ "fieldname": "consumption_details",
+ "fieldtype": "Small 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": "Consumption 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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "depends_on": "invoice_separately_as_consumables",
+ "fieldname": "consumption_invoiced",
+ "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": "Consumption Invoiced",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"default": "",
"fieldname": "status",
"fieldtype": "Select",
@@ -745,10 +912,11 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
+ "restrict_to_domain": "Healthcare",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
index 97d8a02..6d00c25 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
@@ -10,20 +10,29 @@
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
from erpnext.healthcare.doctype.lab_test.lab_test import create_sample_doc
from erpnext.stock.stock_ledger import get_previous_sle
+from erpnext.stock.get_item_details import get_item_details
class ClinicalProcedure(Document):
def validate(self):
if self.consume_stock and not self.status == 'Draft':
if not self.warehouse:
- frappe.throw(("Set warehouse for Procedure {0} ").format(self.name))
+ frappe.throw(_("Set warehouse for Procedure {0} ").format(self.name))
self.set_actual_qty()
+ if self.items:
+ self.invoice_separately_as_consumables = False
+ for item in self.items:
+ if item.invoice_separately_as_consumables == 1:
+ self.invoice_separately_as_consumables = True
+
def before_insert(self):
if self.consume_stock:
set_stock_items(self, self.procedure_template, "Clinical Procedure Template")
self.set_actual_qty();
def after_insert(self):
+ if self.prescription:
+ frappe.db.set_value("Procedure Prescription", self.prescription, "procedure_created", 1)
if self.appointment:
frappe.db.set_value("Patient Appointment", self.appointment, "status", "Closed")
template = frappe.get_doc("Clinical Procedure Template", self.procedure_template)
@@ -38,6 +47,36 @@
create_stock_entry(self)
frappe.db.set_value("Clinical Procedure", self.name, "status", 'Completed')
+ if self.items:
+ consumable_total_amount = 0
+ consumption_details = False
+ for item in self.items:
+ if item.invoice_separately_as_consumables:
+ price_list, price_list_currency = frappe.db.get_values("Price List", {"selling": 1}, ['name', 'currency'])[0]
+ args = {
+ 'doctype': "Sales Invoice",
+ 'item_code': item.item_code,
+ 'company': self.company,
+ 'warehouse': self.warehouse,
+ 'customer': frappe.db.get_value("Patient", self.patient, "customer"),
+ 'selling_price_list': price_list,
+ 'price_list_currency': price_list_currency,
+ 'plc_conversion_rate': 1.0,
+ 'conversion_rate': 1.0
+ }
+ item_details = get_item_details(args)
+ item_price = item_details.price_list_rate * item.transfer_qty
+ item_consumption_details = item_details.item_name+"\t"+str(item.qty)+" "+item.uom+"\t"+str(item_price)
+ consumable_total_amount += item_price
+ if not consumption_details:
+ consumption_details = "Clinical Procedure ("+self.name+"):\n\t"+item_consumption_details
+ else:
+ consumption_details += "\n\t"+item_consumption_details
+ if consumable_total_amount > 0:
+ frappe.db.set_value("Clinical Procedure", self.name, "consumable_total_amount", consumable_total_amount)
+ frappe.db.set_value("Clinical Procedure", self.name, "consumption_details", consumption_details)
+
+
def start(self):
allow_start = self.set_actual_qty()
if allow_start:
@@ -52,16 +91,7 @@
allow_start = True
for d in self.get('items'):
- previous_sle = get_previous_sle({
- "item_code": d.item_code,
- "warehouse": self.warehouse,
- "posting_date": nowdate(),
- "posting_time": nowtime()
- })
-
- # get actual stock at source warehouse
- d.actual_qty = previous_sle.get("qty_after_transaction") or 0
-
+ d.actual_qty = get_stock_qty(d.item_code, self.warehouse)
# validate qty
if not allow_negative_stock and d.actual_qty < d.qty:
allow_start = False
@@ -91,28 +121,14 @@
se_child.expense_account = expense_account
return stock_entry.as_dict()
- def get_item_details(self, args=None):
- item = frappe.db.sql("""select stock_uom, description, image, item_name,
- expense_account, buying_cost_center, item_group from `tabItem`
- where name = %s
- and disabled=0
- and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)""",
- (args.get('item_code'), nowdate()), as_dict = 1)
- if not item:
- frappe.throw(_("Item {0} is not active or end of life has been reached").format(args.get('item_code')))
-
- item = item[0]
-
- ret = {
- 'uom' : item.stock_uom,
- 'stock_uom' : item.stock_uom,
- 'item_name' : item.item_name,
- 'quantity' : 0,
- 'transfer_qty' : 0,
- 'conversion_factor' : 1
- }
- return ret
-
+@frappe.whitelist()
+def get_stock_qty(item_code, warehouse):
+ return get_previous_sle({
+ "item_code": item_code,
+ "warehouse": warehouse,
+ "posting_date": nowdate(),
+ "posting_time": nowtime()
+ }).get("qty_after_transaction") or 0
@frappe.whitelist()
def set_stock_items(doc, stock_detail_parent, parenttype):
@@ -130,6 +146,8 @@
se_child.conversion_factor = flt(d["conversion_factor"])
if d["batch_no"]:
se_child.batch_no = d["batch_no"]
+ if parenttype == "Clinical Procedure Template":
+ se_child.invoice_separately_as_consumables = d["invoice_separately_as_consumables"]
return doc
def get_item_dict(table, parent, parenttype):
@@ -165,6 +183,8 @@
procedure.patient_age = appointment.patient_age
procedure.patient_sex = appointment.patient_sex
procedure.procedure_template = appointment.procedure_template
+ procedure.procedure_prescription = appointment.procedure_prescription
+ procedure.invoiced = appointment.invoiced
procedure.medical_department = appointment.department
procedure.start_date = appointment.appointment_date
procedure.start_time = appointment.appointment_time
diff --git a/erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json b/erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json
index c0a3247..a974f21 100644
--- a/erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json
+++ b/erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json
@@ -14,6 +14,7 @@
"fields": [
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
@@ -46,6 +47,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -77,6 +79,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -108,6 +111,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -139,6 +143,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -171,6 +176,39 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "invoice_separately_as_consumables",
+ "fieldtype": "Check",
+ "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": "Invoice Separately as Consumables",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -201,6 +239,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -233,6 +272,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -264,6 +304,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -296,6 +337,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -327,6 +369,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -367,7 +410,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-03-28 14:34:03.796229",
+ "modified": "2018-07-26 17:05:29.402908",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Clinical Procedure Item",
@@ -381,5 +424,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json
index 35c0ff5..df56918 100644
--- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json
+++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json
@@ -1,7 +1,7 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
- "allow_import": 0,
+ "allow_import": 1,
"allow_rename": 1,
"autoname": "field:template",
"beta": 1,
@@ -16,6 +16,7 @@
"fields": [
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -43,11 +44,12 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "translatable": 0,
+ "unique": 1
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -75,11 +77,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -107,11 +110,12 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -139,11 +143,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -169,11 +174,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -200,11 +206,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -232,11 +239,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -263,11 +271,12 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -294,11 +303,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -325,11 +335,12 @@
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -357,11 +368,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -389,11 +401,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
@@ -421,11 +434,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -453,16 +467,17 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fetch_from": "sample.sample_uom",
+ "fetch_from": "sample.sample_uom",
"fieldname": "sample_uom",
"fieldtype": "Data",
"hidden": 0,
@@ -486,11 +501,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -517,11 +533,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -547,11 +564,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -578,11 +596,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -609,11 +628,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -640,11 +660,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -672,7 +693,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
}
],
@@ -686,7 +707,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-05-16 22:43:29.674822",
+ "modified": "2018-08-08 13:00:06.260997",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Clinical Procedure Template",
@@ -780,5 +801,6 @@
"sort_order": "DESC",
"title_field": "template",
"track_changes": 1,
- "track_seen": 1
-}
+ "track_seen": 1,
+ "track_views": 0
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.py b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
index 53a1741..9028545 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
@@ -4,6 +4,33 @@
from __future__ import unicode_literals
from frappe.model.document import Document
+import frappe
+from frappe.utils import getdate
+import datetime
class FeeValidity(Document):
pass
+
+def update_fee_validity(fee_validity, date, ref_invoice=None):
+ max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit")
+ valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days")
+ if not valid_days:
+ valid_days = 1
+ if not max_visit:
+ max_visit = 1
+ date = getdate(date)
+ valid_till = date + datetime.timedelta(days=int(valid_days))
+ fee_validity.max_visit = max_visit
+ fee_validity.visited = 1
+ fee_validity.valid_till = valid_till
+ fee_validity.ref_invoice = ref_invoice
+ fee_validity.save(ignore_permissions=True)
+ return fee_validity
+
+
+def create_fee_validity(practitioner, patient, date, ref_invoice=None):
+ fee_validity = frappe.new_doc("Fee Validity")
+ fee_validity.practitioner = practitioner
+ fee_validity.patient = patient
+ fee_validity = update_fee_validity(fee_validity, date, ref_invoice)
+ return fee_validity
diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
index 64222ad..b8305d7 100644
--- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
@@ -5,33 +5,35 @@
import frappe
import unittest
-from erpnext.healthcare.doctype.patient_appointment.patient_appointment import invoice_appointment
from frappe.utils.make_random import get_random
-from frappe.utils import nowdate, add_days
-# test_records = frappe.get_test_records('Fee Validity')
+from frappe.utils import nowdate, add_days, getdate
+
+test_dependencies = ["Company"]
class TestFeeValidity(unittest.TestCase):
def test_fee_validity(self):
+ frappe.db.sql("""delete from `tabPatient Appointment`""")
+ frappe.db.sql("""delete from `tabFee Validity`""")
patient = get_random("Patient")
practitioner = get_random("Healthcare Practitioner")
department = get_random("Medical Department")
if not patient:
patient = frappe.new_doc("Patient")
- patient.patient_name = "Test Patient"
+ patient.patient_name = "_Test Patient"
patient.sex = "Male"
patient.save(ignore_permissions=True)
patient = patient.name
if not department:
medical_department = frappe.new_doc("Medical Department")
- medical_department.department = "Test Medical Department"
+ medical_department.department = "_Test Medical Department"
medical_department.save(ignore_permissions=True)
department = medical_department.name
if not practitioner:
practitioner = frappe.new_doc("Healthcare Practitioner")
- practitioner.first_name = "Amit Jain"
+ practitioner.first_name = "_Test Healthcare Practitioner"
practitioner.department = department
practitioner.save(ignore_permissions=True)
practitioner = practitioner.name
@@ -42,18 +44,22 @@
frappe.db.set_value("Healthcare Settings", None, "valid_days", 7)
appointment = create_appointment(patient, practitioner, nowdate(), department)
- invoice = frappe.db.get_value("Patient Appointment", appointment.name, "sales_invoice")
- self.assertEqual(invoice, None)
+ invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced")
+ self.assertEqual(invoiced, 0)
+
invoice_appointment(appointment)
+
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), department)
- invoice = frappe.db.get_value("Patient Appointment", appointment.name, "sales_invoice")
- self.assertTrue(invoice)
+ invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced")
+ self.assertTrue(invoiced)
+
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 5), department)
- invoice = frappe.db.get_value("Patient Appointment", appointment.name, "sales_invoice")
- self.assertEqual(invoice, None)
+ invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced")
+ self.assertEqual(invoiced, 0)
+
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 10), department)
- invoice = frappe.db.get_value("Patient Appointment", appointment.name, "sales_invoice")
- self.assertEqual(invoice, None)
+ invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced")
+ self.assertEqual(invoiced, 0)
def create_appointment(patient, practitioner, appointment_date, department):
appointment = frappe.new_doc("Patient Appointment")
@@ -64,3 +70,34 @@
appointment.company = "_Test Company"
appointment.save(ignore_permissions=True)
return appointment
+
+def invoice_appointment(appointment_doc):
+ if not appointment_doc.name:
+ return False
+ sales_invoice = frappe.new_doc("Sales Invoice")
+ sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer")
+ sales_invoice.due_date = getdate()
+ sales_invoice.is_pos = 0
+ sales_invoice.company = appointment_doc.company
+ sales_invoice.debit_to = "_Test Receivable - _TC"
+
+ create_invoice_items(appointment_doc, sales_invoice)
+
+ sales_invoice.save(ignore_permissions=True)
+ sales_invoice.submit()
+
+def create_invoice_items(appointment, invoice):
+ item_line = invoice.append("items")
+ item_line.item_name = "Consulting Charges"
+ item_line.description = "Consulting Charges: " + appointment.practitioner
+ item_line.uom = "Nos"
+ item_line.conversion_factor = 1
+ item_line.income_account = "_Test Account Cost for Goods Sold - _TC"
+ item_line.cost_center = "_Test Cost Center - _TC"
+ item_line.rate = 250
+ item_line.amount = 250
+ item_line.qty = 1
+ item_line.reference_dt = "Patient Appointment"
+ item_line.reference_dn = appointment.name
+
+ return invoice
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
index f2dc849..efca484 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
@@ -27,9 +27,22 @@
}
};
});
+ set_query_service_item(frm, 'inpatient_visit_charge_item');
+ set_query_service_item(frm, 'op_consulting_charge_item');
}
});
+var set_query_service_item = function(frm, service_item_field) {
+ frm.set_query(service_item_field, function() {
+ return {
+ filters: {
+ 'is_sales_item': 1,
+ 'is_stock_item': 0
+ }
+ };
+ });
+};
+
frappe.ui.form.on("Healthcare Practitioner", "user_id",function(frm) {
if(frm.doc.user_id){
frappe.call({
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
index e7c575d..ad68924 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
@@ -201,7 +201,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
- "search_index": 0,
+ "search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
@@ -535,6 +535,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "op_consulting_charge_item",
+ "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": "Out Patient Consulting Charge Item",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "op_consulting_charge",
"fieldtype": "Currency",
"hidden": 0,
@@ -568,6 +601,102 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "column_break_18",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "inpatient_visit_charge_item",
+ "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": "Inpatient Visit Charge Item",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "inpatient_visit_charge",
+ "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": "Inpatient Visit Charge",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "contacts_and_address",
"fieldtype": "Section Break",
"hidden": 0,
@@ -798,7 +927,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-07-10 11:18:58.760297",
+ "modified": "2018-08-06 16:45:37.899084",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Practitioner",
@@ -873,5 +1002,6 @@
"sort_order": "DESC",
"title_field": "first_name",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
index 753ecd1..8a087dd 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
@@ -21,6 +21,10 @@
def validate(self):
validate_party_accounts(self)
+ if self.inpatient_visit_charge_item:
+ validate_service_item(self.inpatient_visit_charge_item, "Configure a service Item for Inpatient Visit Charge Item")
+ if self.op_consulting_charge_item:
+ validate_service_item(self.op_consulting_charge_item, "Configure a service Item for Out Patient Consulting Charge Item")
if self.user_id:
self.validate_for_enabled_user_id()
@@ -57,3 +61,7 @@
def on_trash(self):
delete_contact_and_address('Healthcare Practitioner', self.name)
+
+def validate_service_item(item, msg):
+ if frappe.db.get_value("Item", item, "is_stock_item") == 1:
+ frappe.throw(_(msg))
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py
index 3c01ab0..635464e 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py
@@ -9,10 +9,6 @@
{
'label': _('Appointments and Patient Encounters'),
'items': ['Patient Appointment', 'Patient Encounter']
- },
- {
- 'label': _('Lab Tests'),
- 'items': ['Lab Test']
}
]
}
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
index 945817c..7d6b6c1 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
@@ -1,7 +1,7 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
- "allow_import": 0,
+ "allow_import": 1,
"allow_rename": 1,
"autoname": "field:healthcare_service_unit_name",
"beta": 1,
@@ -43,7 +43,7 @@
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
- "unique": 0
+ "unique": 1
},
{
"allow_bulk_edit": 0,
@@ -116,7 +116,7 @@
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
- "bold": 0,
+ "bold": 1,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_group != 1",
@@ -246,7 +246,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
- "search_index": 0,
+ "search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
@@ -258,10 +258,10 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "default": "0",
+ "default": "",
"depends_on": "eval:doc.inpatient_occupancy == 1",
- "fieldname": "occupied",
- "fieldtype": "Check",
+ "fieldname": "occupancy_status",
+ "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -269,9 +269,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Occupied",
+ "label": "Occupancy Status",
"length": 0,
"no_copy": 1,
+ "options": "Vacant\nOccupied",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -460,7 +461,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-07-17 17:40:18.867327",
+ "modified": "2018-08-08 12:57:12.709806",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Service Unit",
@@ -535,5 +536,6 @@
"sort_order": "DESC",
"title_field": "healthcare_service_unit_name",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js
index 4eb9475..a03b579 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js
@@ -1,3 +1,35 @@
frappe.treeview_settings["Healthcare Service Unit"] = {
- ignore_fields:["parent_healthcare_service_unit"]
+ breadcrumbs: "Healthcare Service Unit",
+ title: __("Healthcare Service Unit"),
+ get_tree_root: false,
+ filters: [{
+ fieldname: "company",
+ fieldtype: "Select",
+ options: erpnext.utils.get_tree_options("company"),
+ label: __("Company"),
+ default: erpnext.utils.get_tree_default("company")
+ }],
+ get_tree_nodes: 'erpnext.healthcare.utils.get_children',
+ ignore_fields:["parent_healthcare_service_unit"],
+ onrender: function(node) {
+ if (node.data.occupied_out_of_vacant!==undefined){
+ $('<span class="balance-area pull-right text-muted small">'
+ + " " + node.data.occupied_out_of_vacant
+ + '</span>').insertBefore(node.$ul);
+ }
+ if (node.data && node.data.inpatient_occupancy!==undefined) {
+ if (node.data.inpatient_occupancy == 1){
+ if (node.data.occupancy_status == "Occupied"){
+ $('<span class="balance-area pull-right small">'
+ + " " + node.data.occupancy_status
+ + '</span>').insertBefore(node.$ul);
+ }
+ if (node.data.occupancy_status == "Vacant"){
+ $('<span class="balance-area pull-right text-muted small">'
+ + " " + node.data.occupancy_status
+ + '</span>').insertBefore(node.$ul);
+ }
+ }
+ }
+ },
};
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
index 6394ce7..40681e9 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
+++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
@@ -1,8 +1,8 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
"autoname": "field:service_unit_type",
"beta": 0,
"creation": "2018-07-11 16:47:51.414675",
@@ -43,7 +43,7 @@
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
- "unique": 0
+ "unique": 1
},
{
"allow_bulk_edit": 0,
@@ -351,8 +351,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "rate",
- "fieldtype": "Currency",
+ "fieldname": "no_of_hours",
+ "fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -360,7 +360,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Rate / UOM",
+ "label": "UOM Conversion in Hours",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -414,6 +414,38 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "rate",
+ "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": "Rate / UOM",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
@@ -515,7 +547,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-07-13 16:54:59.131606",
+ "modified": "2018-08-08 13:00:23.751635",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Service Unit Type",
@@ -545,10 +577,12 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
+ "restrict_to_domain": "Healthcare",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "service_unit_type",
"track_changes": 0,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py
index e0d380b..727d035 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py
+++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py
@@ -8,6 +8,11 @@
from frappe.model.document import Document
class HealthcareServiceUnitType(Document):
+ def validate(self):
+ if self.is_billable == 1:
+ if not self.uom or not self.item_group or not self.description or not self.no_of_hours > 0:
+ frappe.throw(_("Configure Item Fields like UOM, Item Group, Description and No of Hours."))
+
def after_insert(self):
if self.inpatient_occupancy and self.is_billable:
create_item(self)
@@ -22,13 +27,16 @@
def on_update(self):
if(self.change_in_item and self.is_billable == 1 and self.item):
updating_item(self)
- if not item_price_exist(self):
- if(self.test_rate != 0.0):
+ item_price = item_price_exist(self)
+ if not item_price:
+ if(self.rate != 0.0):
price_list_name = frappe.db.get_value("Price List", {"selling": 1})
- if(self.test_rate):
- make_item_price(self.test_code, price_list_name, self.test_rate)
+ if(self.rate):
+ make_item_price(self.item_code, price_list_name, self.rate)
else:
- make_item_price(self.test_code, price_list_name, 0.0)
+ make_item_price(self.item_code, price_list_name, 0.0)
+ else:
+ frappe.db.set_value("Item Price", item_price, "price_list_rate", self.rate)
frappe.db.set_value(self.doctype,self.name,"change_in_item",0)
elif(self.is_billable == 0 and self.item):
@@ -40,7 +48,7 @@
"doctype": "Item Price",
"item_code": doc.item_code})
if(item_price):
- return True
+ return item_price[0][0]
else:
return False
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
index 8e98fee..22fbf50 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
@@ -23,5 +23,19 @@
}
};
});
+ set_query_service_item(frm, 'inpatient_visit_charge_item');
+ set_query_service_item(frm, 'op_consulting_charge_item');
+ set_query_service_item(frm, 'clinical_procedure_consumable_item');
}
});
+
+var set_query_service_item = function(frm, service_item_field) {
+ frm.set_query(service_item_field, function() {
+ return {
+ filters: {
+ 'is_sales_item': 1,
+ 'is_stock_item': 0
+ }
+ };
+ });
+};
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
index 0bd8534..24c3cd9 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
@@ -248,6 +248,40 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "default": "0",
+ "description": "Manage Appointment Invoice submit and cancel automatically for Patient Encounter",
+ "fieldname": "manage_appointment_invoice_automatically",
+ "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": "Manage Appointment Invoice Automatically",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "max_visit",
"fieldtype": "Int",
"hidden": 0,
@@ -312,6 +346,168 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
+ "fieldname": "healthcare_service_items",
+ "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": "Healthcare Service Items",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "inpatient_visit_charge_item",
+ "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": "Inpatient Visit Charge Item",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "op_consulting_charge_item",
+ "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": "Out Patient Consulting Charge Item",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_13",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "clinical_procedure_consumable_item",
+ "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": "Clinical Procedure Consumable Item",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "columns": 0,
"fieldname": "out_patient_sms_alerts",
"fieldtype": "Section Break",
"hidden": 0,
@@ -804,6 +1000,38 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "create_test_on_si_submit",
+ "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": "Create Lab Test(s) on Sales Invoice Submit",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"description": "Create documents for sample collection",
"fieldname": "require_sample_collection",
"fieldtype": "Check",
@@ -1099,7 +1327,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-07-16 14:00:04.171717",
+ "modified": "2018-08-03 15:18:36.631441",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Settings",
@@ -1134,5 +1362,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py
index 8c3cdfe..8555e80 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py
@@ -10,13 +10,19 @@
import json
class HealthcareSettings(Document):
- def validate(self):
- for key in ["collect_registration_fee","manage_customer","patient_master_name",
- "require_test_result_approval","require_sample_collection", "default_medical_code_standard"]:
- frappe.db.set_default(key, self.get(key, ""))
- if(self.collect_registration_fee):
- if self.registration_fee <= 0 :
- frappe.throw(_("Registration fee can not be Zero"))
+ def validate(self):
+ for key in ["collect_registration_fee","manage_customer","patient_master_name",
+ "require_test_result_approval","require_sample_collection", "default_medical_code_standard"]:
+ frappe.db.set_default(key, self.get(key, ""))
+ if(self.collect_registration_fee):
+ if self.registration_fee <= 0 :
+ frappe.throw(_("Registration fee can not be Zero"))
+ if self.inpatient_visit_charge_item:
+ validate_service_item(self.inpatient_visit_charge_item, "Configure a service Item for Inpatient Visit Charge Item")
+ if self.op_consulting_charge_item:
+ validate_service_item(self.op_consulting_charge_item, "Configure a service Item for Out Patient Consulting Charge Item")
+ if self.clinical_procedure_consumable_item:
+ validate_service_item(self.clinical_procedure_consumable_item, "Configure a service Item for Clinical Procedure Consumable Item")
@frappe.whitelist()
def get_sms_text(doc):
@@ -67,3 +73,7 @@
if(parent_field):
return frappe.db.get_value("Party Account",
{"parentfield": parent_field, "parent": parent, "company": company}, "account")
+
+def validate_service_item(item, msg):
+ if frappe.db.get_value("Item", item, "is_stock_item") == 1:
+ frappe.throw(_(msg))
diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
index 2ac498d..62dc198 100644
--- a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
+++ b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
@@ -104,7 +104,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
- "search_index": 0,
+ "search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
@@ -140,6 +140,39 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "fieldname": "invoiced",
+ "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": "Invoiced",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "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
}
],
"has_web_view": 0,
@@ -152,7 +185,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-07-17 18:26:46.009878",
+ "modified": "2018-08-06 16:46:54.699133",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Inpatient Occupancy",
@@ -166,5 +199,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js
index 936c682..67c12f6 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js
@@ -94,7 +94,7 @@
filters: {
"is_group": 0,
"service_unit_type": dialog.get_value("service_unit_type"),
- "occupied" : 0
+ "occupancy_status" : "Vacant"
}
};
};
@@ -166,7 +166,7 @@
filters: {
"is_group": 0,
"service_unit_type": dialog.get_value("service_unit_type"),
- "occupied" : 0
+ "occupancy_status" : "Vacant"
}
};
};
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
index 50f17e9..92c11fb 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
@@ -968,6 +968,7 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
+ "restrict_to_domain": "Healthcare",
"search_fields": "patient",
"show_name_in_global_search": 0,
"sort_field": "modified",
@@ -976,4 +977,4 @@
"track_changes": 1,
"track_seen": 0,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
index 07cd9e4..c107cd7 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
@@ -69,29 +69,75 @@
inpatient_record.save(ignore_permissions = True)
@frappe.whitelist()
-def schedule_discharge(patient, encounter_id, practitioner):
+def schedule_discharge(patient, encounter_id=None, practitioner=None):
inpatient_record_id = frappe.db.get_value('Patient', patient, 'inpatient_record')
if inpatient_record_id:
inpatient_record = frappe.get_doc("Inpatient Record", inpatient_record_id)
inpatient_record.discharge_practitioner = practitioner
inpatient_record.discharge_encounter = encounter_id
inpatient_record.status = "Discharge Scheduled"
+
+ check_out_inpatient(inpatient_record)
+
inpatient_record.save(ignore_permissions = True)
frappe.db.set_value("Patient", patient, "inpatient_status", "Discharge Scheduled")
-def discharge_patient(inpatient_record):
+def check_out_inpatient(inpatient_record):
if inpatient_record.inpatient_occupancies:
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
if inpatient_occupancy.left != 1:
inpatient_occupancy.left = True
inpatient_occupancy.check_out = now_datetime()
- frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupied", False)
+ frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
+def discharge_patient(inpatient_record):
+ validate_invoiced_inpatient(inpatient_record)
inpatient_record.discharge_date = today()
inpatient_record.status = "Discharged"
inpatient_record.save(ignore_permissions = True)
+def validate_invoiced_inpatient(inpatient_record):
+ pending_invoices = []
+ if inpatient_record.inpatient_occupancies:
+ service_unit_names = False
+ for inpatient_occupancy in inpatient_record.inpatient_occupancies:
+ if inpatient_occupancy.invoiced != 1:
+ if service_unit_names:
+ service_unit_names += ", " + inpatient_occupancy.service_unit
+ else:
+ service_unit_names = inpatient_occupancy.service_unit
+ if service_unit_names:
+ pending_invoices.append("Inpatient Occupancy (" + service_unit_names + ")")
+
+ docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"]
+
+ for doc in docs:
+ doc_name_list = get_inpatient_docs_not_invoiced(doc, inpatient_record)
+ if doc_name_list:
+ pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices)
+
+ if pending_invoices:
+ frappe.throw(_("Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}").format(", "
+ .join(pending_invoices)))
+
+def get_pending_doc(doc, doc_name_list, pending_invoices):
+ if doc_name_list:
+ doc_ids = False
+ for doc_name in doc_name_list:
+ if doc_ids:
+ doc_ids += ", "+doc_name.name
+ else:
+ doc_ids = doc_name.name
+ if doc_ids:
+ pending_invoices.append(doc + " (" + doc_ids + ")")
+
+ return pending_invoices
+
+def get_inpatient_docs_not_invoiced(doc, inpatient_record):
+ return frappe.db.get_list(doc, filters = {"patient": inpatient_record.patient,
+ "inpatient_record": inpatient_record.name, "invoiced": 0})
+
def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None):
inpatient_record.admitted_datetime = check_in
inpatient_record.status = "Admitted"
@@ -110,7 +156,7 @@
inpatient_record.save(ignore_permissions = True)
- frappe.db.set_value("Healthcare Service Unit", service_unit, "occupied", True)
+ frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied")
def patient_leave_service_unit(inpatient_record, check_out, leave_from):
if inpatient_record.inpatient_occupancies:
@@ -118,7 +164,7 @@
if inpatient_occupancy.left != 1 and inpatient_occupancy.service_unit == leave_from:
inpatient_occupancy.left = True
inpatient_occupancy.check_out = check_out
- frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupied", False)
+ frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
inpatient_record.save(ignore_permissions = True)
@frappe.whitelist()
diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
index b192064..8849748 100644
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
@@ -7,10 +7,11 @@
import unittest
from frappe.utils import now_datetime, today
from frappe.utils.make_random import get_random
-from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient
+from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
class TestInpatientRecord(unittest.TestCase):
def test_admit_and_discharge(self):
+ frappe.db.sql("""delete from `tabInpatient Record`""")
patient = get_patient()
# Schedule Admission
ip_record = create_inpatient(patient)
@@ -22,13 +23,21 @@
service_unit = get_healthcare_service_unit()
admit_patient(ip_record, service_unit, now_datetime())
self.assertEqual("Admitted", frappe.db.get_value("Patient", patient, "inpatient_status"))
- self.assertEqual(1, frappe.db.get_value("Healthcare Service Unit", service_unit, "occupied"))
+ self.assertEqual("Occupied", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
# Discharge
- discharge_patient(ip_record)
+ schedule_discharge(patient=patient)
+ self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
+
+ ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
+ # Validate Pending Invoices
+ self.assertRaises(frappe.ValidationError, ip_record.discharge)
+ mark_invoiced_inpatient_occupancy(ip_record1)
+
+ discharge_patient(ip_record1)
+
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
- self.assertEqual(0, frappe.db.get_value("Healthcare Service Unit", service_unit, "occupied"))
def test_validate_overlap_admission(self):
frappe.db.sql("""delete from `tabInpatient Record`""")
@@ -45,6 +54,12 @@
self.assertRaises(frappe.ValidationError, ip_record_new.save)
frappe.db.sql("""delete from `tabInpatient Record`""")
+def mark_invoiced_inpatient_occupancy(ip_record):
+ if ip_record.inpatient_occupancies:
+ for inpatient_occupancy in ip_record.inpatient_occupancies:
+ inpatient_occupancy.invoiced = 1
+ ip_record.save(ignore_permissions = True)
+
def create_inpatient(patient):
patient_obj = frappe.get_doc('Patient', patient)
inpatient_record = frappe.new_doc('Inpatient Record')
@@ -78,7 +93,7 @@
service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy"
service_unit.service_unit_type = get_service_unit_type()
service_unit.inpatient_occupancy = 1
- service_unit.occupied = 0
+ service_unit.occupancy_status = "Vacant"
service_unit.is_group = 0
service_unit_parent_name = frappe.db.exists({
"doctype": "Healthcare Service Unit",
diff --git a/erpnext/healthcare/doctype/lab_prescription/lab_prescription.json b/erpnext/healthcare/doctype/lab_prescription/lab_prescription.json
index 127bebf..ce6b206 100644
--- a/erpnext/healthcare/doctype/lab_prescription/lab_prescription.json
+++ b/erpnext/healthcare/doctype/lab_prescription/lab_prescription.json
@@ -13,6 +13,7 @@
"fields": [
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -40,16 +41,17 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fetch_from": "test_code.test_name",
+ "fetch_from": "test_code.test_name",
"fieldname": "test_name",
"fieldtype": "Data",
"hidden": 0,
@@ -73,17 +75,19 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "invoice",
- "fieldtype": "Link",
+ "default": "0",
+ "fieldname": "invoiced",
+ "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -91,10 +95,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Invoice",
+ "label": "Invoiced",
"length": 0,
- "no_copy": 0,
- "options": "Sales Invoice",
+ "no_copy": 1,
+ "options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -103,13 +107,14 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
- "search_index": 0,
+ "search_index": 1,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -135,11 +140,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -166,11 +172,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -195,9 +202,9 @@
"remember_last_selected_value": 0,
"report_hide": 1,
"reqd": 0,
- "search_index": 0,
+ "search_index": 1,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
}
],
@@ -211,7 +218,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-05-16 22:43:39.014193",
+ "modified": "2018-08-06 16:53:02.033406",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Prescription",
@@ -226,5 +233,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js
index c3b069d..06637bc 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.js
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.js
@@ -24,11 +24,6 @@
refresh : function(frm){
refresh_field('normal_test_items');
refresh_field('special_test_items');
- if(!frm.doc.__islocal && !frm.doc.invoice && frappe.user.has_role("Accounts User")){
- frm.add_custom_button(__('Make Invoice'), function() {
- make_invoice(frm);
- });
- }
if(frm.doc.__islocal){
frm.add_custom_button(__('Get from Patient Encounter'), function () {
get_lab_test_prescribed(frm);
@@ -166,8 +161,8 @@
<div class="col-xs-1">\
<a data-name="%(name)s" data-lab-test="%(lab_test)s"\
data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\
- data-invoice="%(invoice)s" href="#"><button class="btn btn-default btn-xs">Get Lab Test\
- </button></a></div></div>', {name:y[0], lab_test: y[1], encounter:y[2], invoice:y[3], practitioner:y[4], date:y[5]})).appendTo(html_field);
+ data-invoiced="%(invoiced)s" href="#"><button class="btn btn-default btn-xs">Get Lab Test\
+ </button></a></div></div>', {name:y[0], lab_test: y[1], encounter:y[2], invoiced:y[3], practitioner:y[4], date:y[5]})).appendTo(html_field);
row.find("a").click(function() {
frm.doc.template = $(this).attr("data-lab-test");
frm.doc.prescription = $(this).attr("data-name");
@@ -175,14 +170,11 @@
frm.set_df_property("template", "read_only", 1);
frm.set_df_property("patient", "read_only", 1);
frm.set_df_property("practitioner", "read_only", 1);
- if($(this).attr("data-invoice") != 'null'){
- frm.doc.invoice = $(this).attr("data-invoice");
- refresh_field("invoice");
- }else {
- frm.doc.invoice = "";
- refresh_field("invoice");
+ frm.doc.invoiced = 0;
+ if($(this).attr("data-invoiced") == 1){
+ frm.doc.invoiced = 1;
}
-
+ refresh_field("invoiced");
refresh_field("template");
d.hide();
return false;
@@ -195,24 +187,6 @@
d.show();
};
-var make_invoice = function(frm){
- var doc = frm.doc;
- frappe.call({
- method: "erpnext.healthcare.doctype.lab_test.lab_test.create_invoice",
- args: {company:doc.company, patient:doc.patient, lab_tests: [doc.name], prescriptions:[]},
- callback: function(r){
- if(!r.exc){
- if(r.message){
- /* frappe.show_alert(__('Sales Invoice {0} created',
- ['<a href="#Form/Sales Invoice/'+r.message+'">' + r.message+ '</a>'])); */
- frappe.set_route("Form", "Sales Invoice", r.message);
- }
- cur_frm.reload_doc();
- }
- }
- });
-};
-
cur_frm.cscript.custom_before_submit = function(doc) {
if(doc.normal_test_items){
for(let result in doc.normal_test_items){
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json
index 89b513f..9db3ae5 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.json
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.json
@@ -88,8 +88,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "invoice",
- "fieldtype": "Link",
+ "default": "0",
+ "fieldname": "invoiced",
+ "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -97,10 +98,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Invoice",
+ "label": "Invoiced",
"length": 0,
"no_copy": 1,
- "options": "Sales Invoice",
+ "options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -1586,7 +1587,7 @@
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Healthcare",
- "search_fields": "patient,invoice,practitioner,test_name,sample",
+ "search_fields": "patient,practitioner,test_name,sample",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
@@ -1594,4 +1595,4 @@
"track_changes": 1,
"track_seen": 1,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py
index 767581f..98ab696 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.py
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.py
@@ -4,11 +4,9 @@
from __future__ import unicode_literals
import frappe
-from frappe.model.document import Document
-import json
-from frappe.utils import getdate, cstr
-from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account
from frappe import _
+from frappe.model.document import Document
+from frappe.utils import getdate, cstr
class LabTest(Document):
def on_submit(self):
@@ -31,6 +29,8 @@
def after_insert(self):
if(self.prescription):
frappe.db.set_value("Lab Prescription", self.prescription, "test_created", 1)
+ if frappe.db.get_value("Lab Prescription", self.prescription, 'invoiced') == 1:
+ self.invoiced = True
if not self.test_name and self.template:
self.load_test_from_template()
self.reload()
@@ -60,20 +60,97 @@
def update_lab_test_print_sms_email_status(print_sms_email, name):
frappe.db.set_value("Lab Test",name,print_sms_email,1)
-def create_lab_test_doc(invoice, encounter, patient, template):
- #create Test Result for template, copy vals from Invoice
+@frappe.whitelist()
+def create_multiple(doctype, docname):
+ lab_test_created = False
+ if doctype == "Sales Invoice":
+ lab_test_created = create_lab_test_from_invoice(docname)
+ elif doctype == "Patient Encounter":
+ lab_test_created = create_lab_test_from_encounter(docname)
+
+ if lab_test_created:
+ frappe.msgprint(_("Lab Test(s) "+lab_test_created+" created."))
+ else:
+ frappe.msgprint(_("No Lab Test created"))
+
+def create_lab_test_from_encounter(encounter_id):
+ lab_test_created = False
+ encounter = frappe.get_doc("Patient Encounter", encounter_id)
+
+ lab_test_ids = frappe.db.sql("""select lp.name, lp.test_code, lp.invoiced
+ from `tabPatient Encounter` et, `tabLab Prescription` lp
+ where et.patient=%s and lp.parent=%s and
+ lp.parent=et.name and lp.test_created=0 and et.docstatus=1""", (encounter.patient, encounter_id))
+
+ if lab_test_ids:
+ patient = frappe.get_doc("Patient", encounter.patient)
+ for lab_test_id in lab_test_ids:
+ template = get_lab_test_template(lab_test_id[1])
+ if template:
+ lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template)
+ lab_test.save(ignore_permissions = True)
+ frappe.db.set_value("Lab Prescription", lab_test_id[0], "test_created", 1)
+ if not lab_test_created:
+ lab_test_created = lab_test.name
+ else:
+ lab_test_created += ", "+lab_test.name
+ return lab_test_created
+
+
+def create_lab_test_from_invoice(invoice_name):
+ lab_test_created = False
+ invoice = frappe.get_doc("Sales Invoice", invoice_name)
+ if invoice.patient:
+ patient = frappe.get_doc("Patient", invoice.patient)
+ for item in invoice.items:
+ test_created = 0
+ if item.reference_dt == "Lab Prescription":
+ test_created = frappe.db.get_value("Lab Prescription", item.reference_dn, "test_created")
+ elif item.reference_dt == "Lab Test":
+ test_created = 1
+ if test_created != 1:
+ template = get_lab_test_template(item.item_code)
+ if template:
+ lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template)
+ if item.reference_dt == "Lab Prescription":
+ lab_test.prescription = item.reference_dn
+ lab_test.save(ignore_permissions = True)
+ if item.reference_dt != "Lab Prescription":
+ frappe.db.set_value("Sales Invoice Item", item.name, "reference_dt", "Lab Test")
+ frappe.db.set_value("Sales Invoice Item", item.name, "reference_dn", lab_test.name)
+ if not lab_test_created:
+ lab_test_created = lab_test.name
+ else:
+ lab_test_created += ", "+lab_test.name
+ return lab_test_created
+
+def get_lab_test_template(item):
+ template_id = check_template_exists(item)
+ if template_id:
+ return frappe.get_doc("Lab Test Template", template_id)
+ return False
+
+def check_template_exists(item):
+ template_exists = frappe.db.exists(
+ "Lab Test Template",
+ {
+ 'item': item
+ }
+ )
+ if template_exists:
+ return template_exists
+ return False
+
+def create_lab_test_doc(invoiced, practitioner, patient, template):
lab_test = frappe.new_doc("Lab Test")
- if(invoice):
- lab_test.invoice = invoice
- if(encounter):
- lab_test.practitioner = encounter.practitioner
+ lab_test.invoiced = invoiced
+ lab_test.practitioner = practitioner
lab_test.patient = patient.name
lab_test.patient_age = patient.get_age()
lab_test.patient_sex = patient.sex
lab_test.email = patient.email
lab_test.mobile = patient.mobile
lab_test.department = template.department
- lab_test.test_name = template.test_name
lab_test.template = template.name
lab_test.test_group = template.test_group
lab_test.result_date = getdate()
@@ -133,7 +210,7 @@
#create Sample Collection for template, copy vals from Invoice
sample_collection = frappe.new_doc("Sample Collection")
if(invoice):
- sample_collection.invoice = invoice
+ sample_collection.invoiced = True
sample_collection.patient = patient.name
sample_collection.patient_age = patient.get_age()
sample_collection.patient_sex = patient.sex
@@ -146,24 +223,6 @@
return sample_collection
-@frappe.whitelist()
-def create_lab_test_from_desk(patient, template, prescription, invoice=None):
- lab_test_exist = frappe.db.exists({
- "doctype": "Lab Test",
- "prescription": prescription
- })
- if lab_test_exist:
- return
- template = frappe.get_doc("Lab Test Template", template)
- #skip the loop if there is no test_template for Item
- if not (template):
- return
- patient = frappe.get_doc("Patient", patient)
- encounter_id = frappe.get_value("Lab Prescription", prescription, "parent")
- encounter = frappe.get_doc("Patient Encounter", encounter_id)
- lab_test = create_lab_test(patient, template, prescription, encounter, invoice)
- return lab_test.name
-
def create_sample_collection(lab_test, template, patient, invoice):
if(frappe.db.get_value("Healthcare Settings", None, "require_sample_collection") == "1"):
sample_collection = create_sample_doc(template, patient, invoice)
@@ -211,16 +270,10 @@
if(prescription):
lab_test.prescription = prescription
if(invoice):
- frappe.db.set_value("Lab Prescription", prescription, "invoice", invoice)
+ frappe.db.set_value("Lab Prescription", prescription, "invoiced", True)
lab_test.save(ignore_permissions=True) # insert the result
return lab_test
-def create_lab_test(patient, template, prescription, encounter, invoice):
- lab_test = create_lab_test_doc(invoice, encounter, patient, template)
- lab_test = create_sample_collection(lab_test, template, patient, invoice)
- lab_test = load_result_format(lab_test, template, prescription, invoice)
- return lab_test
-
@frappe.whitelist()
def get_employee_by_user_id(user_id):
emp_id = frappe.db.get_value("Employee",{"user_id":user_id})
@@ -248,49 +301,7 @@
if medical_record_id and medical_record_id[0][0]:
frappe.delete_doc("Patient Medical Record", medical_record_id[0][0])
-def create_item_line(test_code, sales_invoice):
- if test_code:
- item = frappe.get_doc("Item", test_code)
- if item:
- if not item.disabled:
- sales_invoice_line = sales_invoice.append("items")
- sales_invoice_line.item_code = item.item_code
- sales_invoice_line.item_name = item.item_name
- sales_invoice_line.qty = 1.0
- sales_invoice_line.description = item.description
-
-@frappe.whitelist()
-def create_invoice(company, patient, lab_tests, prescriptions):
- test_ids = json.loads(lab_tests)
- line_ids = json.loads(prescriptions)
- if not test_ids and not line_ids:
- return
- sales_invoice = frappe.new_doc("Sales Invoice")
- sales_invoice.customer = frappe.get_value("Patient", patient, "customer")
- sales_invoice.due_date = getdate()
- sales_invoice.is_pos = '0'
- sales_invoice.debit_to = get_receivable_account(company)
- for line in line_ids:
- test_code = frappe.get_value("Lab Prescription", line, "test_code")
- create_item_line(test_code, sales_invoice)
- for test in test_ids:
- template = frappe.get_value("Lab Test", test, "template")
- test_code = frappe.get_value("Lab Test Template", template, "item")
- create_item_line(test_code, sales_invoice)
- sales_invoice.set_missing_values()
- sales_invoice.save()
- #set invoice in lab test
- for test in test_ids:
- frappe.db.set_value("Lab Test", test, "invoice", sales_invoice.name)
- prescription = frappe.db.get_value("Lab Test", test, "prescription")
- if prescription:
- frappe.db.set_value("Lab Prescription", prescription, "invoice", sales_invoice.name)
- #set invoice in prescription
- for line in line_ids:
- frappe.db.set_value("Lab Prescription", line, "invoice", sales_invoice.name)
- return sales_invoice.name
-
@frappe.whitelist()
def get_lab_test_prescribed(patient):
- return frappe.db.sql("""select cp.name, cp.test_code, cp.parent, cp.invoice, ct.practitioner, ct.encounter_date from `tabPatient Encounter` ct,
+ return frappe.db.sql("""select cp.name, cp.test_code, cp.parent, cp.invoiced, ct.practitioner, ct.encounter_date from `tabPatient Encounter` ct,
`tabLab Prescription` cp where ct.patient=%s and cp.parent=ct.name and cp.test_created=0""", (patient))
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test_list.js b/erpnext/healthcare/doctype/lab_test/lab_test_list.js
index c36c115..1f6a12f 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test_list.js
+++ b/erpnext/healthcare/doctype/lab_test/lab_test_list.js
@@ -2,7 +2,7 @@
(c) ESS 2015-16
*/
frappe.listview_settings['Lab Test'] = {
- add_fields: ["name", "status", "invoice"],
+ add_fields: ["name", "status", "invoiced"],
filters:[["docstatus","=","0"]],
get_indicator: function(doc) {
if(doc.status=="Approved"){
@@ -11,5 +11,52 @@
if(doc.status=="Rejected"){
return [__("Rejected"), "yellow", "status,=,Rejected"];
}
+ },
+ onload: function(listview) {
+ listview.page.add_menu_item(__("Create Multiple"), function() {
+ create_multiple_dialog(listview);
+ });
}
};
+
+var create_multiple_dialog = function(listview){
+ var dialog = new frappe.ui.Dialog({
+ title: 'Create Multiple Lab Test',
+ width: 100,
+ fields: [
+ {fieldtype: "Link", label: "Patient", fieldname: "patient", options: "Patient", reqd: 1},
+ {fieldtype: "Select", label: "Invoice / Patient Encounter", fieldname: "doctype",
+ options: "\nSales Invoice\nPatient Encounter", reqd: 1},
+ {fieldtype: "Dynamic Link", fieldname: "docname", options: "doctype", reqd: 1,
+ get_query: function(){
+ return {
+ filters: {
+ "patient": dialog.get_value("patient"),
+ "docstatus": 1
+ }
+ };
+ }
+ }
+ ],
+ primary_action_label: __("Create Lab Test"),
+ primary_action : function(){
+ frappe.call({
+ method: 'erpnext.healthcare.doctype.lab_test.lab_test.create_multiple',
+ args:{
+ 'doctype': dialog.get_value("doctype"),
+ 'docname': dialog.get_value("docname")
+ },
+ callback: function(data) {
+ if(!data.exc){
+ listview.refresh();
+ }
+ },
+ freeze: true,
+ freeze_message: "Creating Lab Test..."
+ });
+ dialog.hide();
+ }
+ });
+
+ dialog.show();
+};
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 d76fb29..62a3b30 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
@@ -12,13 +12,16 @@
#Item and Price List update --> if (change_in_item)
if(self.change_in_item and self.is_billable == 1 and self.item):
updating_item(self)
- if not item_price_exist(self):
+ item_price = item_price_exist(self)
+ if not item_price:
if(self.test_rate != 0.0):
price_list_name = frappe.db.get_value("Price List", {"selling": 1})
if(self.test_rate):
make_item_price(self.test_code, price_list_name, self.test_rate)
else:
make_item_price(self.test_code, price_list_name, 0.0)
+ else:
+ frappe.db.set_value("Item Price", item_price, "price_list_rate", self.test_rate)
frappe.db.set_value(self.doctype,self.name,"change_in_item",0)
elif(self.is_billable == 0 and self.item):
@@ -43,7 +46,7 @@
"doctype": "Item Price",
"item_code": doc.test_code})
if(item_price):
- return True
+ return item_price[0][0]
else:
return False
diff --git a/erpnext/healthcare/doctype/patient/patient_dashboard.py b/erpnext/healthcare/doctype/patient/patient_dashboard.py
index 098497c..46b1013 100644
--- a/erpnext/healthcare/doctype/patient/patient_dashboard.py
+++ b/erpnext/healthcare/doctype/patient/patient_dashboard.py
@@ -13,6 +13,10 @@
{
'label': _('Lab Tests and Vital Signs'),
'items': ['Lab Test', 'Sample Collection', 'Vital Signs']
+ },
+ {
+ 'label': _('Billing'),
+ 'items': ['Sales Invoice']
}
]
}
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
index 9799018..9338b9f 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
@@ -4,7 +4,6 @@
frappe.ui.form.on('Patient Appointment', {
setup: function(frm) {
frm.custom_make_buttons = {
- 'Sales Invoice': 'Invoice',
'Vital Signs': 'Vital Signs',
'Patient Encounter': 'Patient Encounter'
};
@@ -84,20 +83,21 @@
btn_update_status(frm, "Cancelled");
});
}
-
- if(!frm.doc.__islocal){
- if(frm.doc.sales_invoice && frappe.user.has_role("Accounts User")){
- frm.add_custom_button(__('Invoice'), function() {
- frappe.set_route("Form", "Sales Invoice", frm.doc.sales_invoice);
- },__("View") );
- }
- else if(frm.doc.status != "Cancelled" && frappe.user.has_role("Accounts User")){
- frm.add_custom_button(__('Invoice'), function() {
- btn_invoice_encounter(frm);
- },__("Create"));
- }
- }
frm.set_df_property("get_procedure_from_encounter", "read_only", frm.doc.__islocal ? 0 : 1);
+ frappe.db.get_value('Healthcare Settings', {name: 'Healthcare Settings'}, 'manage_appointment_invoice_automatically', (r) => {
+ if(r.manage_appointment_invoice_automatically == 1){
+ frm.set_df_property("mode_of_payment", "hidden", 0);
+ frm.set_df_property("paid_amount", "hidden", 0);
+ frm.set_df_property("mode_of_payment", "reqd", 1);
+ frm.set_df_property("paid_amount", "reqd", 1);
+ }
+ else{
+ frm.set_df_property("mode_of_payment", "hidden", 1);
+ frm.set_df_property("paid_amount", "hidden", 1);
+ frm.set_df_property("mode_of_payment", "reqd", 0);
+ frm.set_df_property("paid_amount", "reqd", 0);
+ }
+ });
},
check_availability: function(frm) {
var { practitioner, appointment_date } = frm.doc;
@@ -339,21 +339,6 @@
);
};
-var btn_invoice_encounter = function(frm){
- frappe.call({
- doc: frm.doc,
- method:"create_invoice",
- callback: function(data){
- if(!data.exc){
- if(data.message){
- frappe.set_route("Form", "Sales Invoice", data.message);
- }
- cur_frm.reload_doc();
- }
- }
- });
-};
-
frappe.ui.form.on("Patient Appointment", "practitioner", function(frm) {
if(frm.doc.practitioner){
frappe.call({
@@ -364,6 +349,7 @@
},
callback: function (data) {
frappe.model.set_value(frm.doctype,frm.docname, "department",data.message.department);
+ frappe.model.set_value(frm.doctype,frm.docname, "paid_amount",data.message.op_consulting_charge);
}
});
}
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index 960648b..5215c28 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
@@ -108,7 +108,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
- "reqd": 1,
+ "reqd": 0,
"search_index": 1,
"set_only_once": 1,
"translatable": 0,
@@ -617,7 +617,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "depends_on": "eval:!doc.__islocal",
+ "depends_on": "",
"fieldname": "section_break_1",
"fieldtype": "Section Break",
"hidden": 0,
@@ -681,6 +681,71 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "mode_of_payment",
+ "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": "Mode of Payment",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Mode of Payment",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "paid_amount",
+ "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": "Paid Amount",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
@@ -712,8 +777,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "sales_invoice",
- "fieldtype": "Link",
+ "default": "0",
+ "fieldname": "invoiced",
+ "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -721,10 +787,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Sales Invoice",
+ "label": "Invoiced",
"length": 0,
"no_copy": 0,
- "options": "Sales Invoice",
+ "options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -989,4 +1055,4 @@
"track_changes": 1,
"track_seen": 1,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index ad2a933..3e6706f 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -6,12 +6,13 @@
import frappe
from frappe.model.document import Document
import json
-from frappe.utils import getdate
+from frappe.utils import getdate, add_days
from frappe import _
import datetime
from frappe.core.doctype.sms_settings.sms_settings import send_sms
-from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account
from erpnext.hr.doctype.employee.employee import is_holiday
+from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account
+from erpnext.healthcare.utils import validity_exists, service_item_and_practitioner_charge
class PatientAppointment(Document):
def on_update(self):
@@ -38,30 +39,112 @@
visited = fee_validity.visited + 1
frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
if fee_validity.ref_invoice:
- frappe.db.set_value("Patient Appointment", appointment.name, "sales_invoice", fee_validity.ref_invoice)
+ frappe.db.set_value("Patient Appointment", appointment.name, "invoiced", True)
frappe.msgprint(_("{0} has fee validity till {1}").format(appointment.patient, fee_validity.valid_till))
confirm_sms(self)
- def create_invoice(self):
- return invoice_appointment(self)
+ if frappe.db.get_value("Healthcare Settings", None, "manage_appointment_invoice_automatically") == '1' and \
+ frappe.db.get_value("Patient Appointment", self.name, "invoiced") != 1:
+ invoice_appointment(self)
+
+@frappe.whitelist()
+def invoice_appointment(appointment_doc):
+ if not appointment_doc.name:
+ return False
+ sales_invoice = frappe.new_doc("Sales Invoice")
+ sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer")
+ sales_invoice.appointment = appointment_doc.name
+ sales_invoice.due_date = getdate()
+ sales_invoice.is_pos = True
+ sales_invoice.company = appointment_doc.company
+ sales_invoice.debit_to = get_receivable_account(appointment_doc.company)
+
+ item_line = sales_invoice.append("items")
+ service_item, practitioner_charge = service_item_and_practitioner_charge(appointment_doc)
+ item_line.item_code = service_item
+ item_line.description = "Consulting Charges: " + appointment_doc.practitioner
+ item_line.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company)
+ item_line.rate = practitioner_charge
+ item_line.amount = practitioner_charge
+ item_line.qty = 1
+ item_line.reference_dt = "Patient Appointment"
+ item_line.reference_dn = appointment_doc.name
+
+ payments_line = sales_invoice.append("payments")
+ payments_line.mode_of_payment = appointment_doc.mode_of_payment
+ payments_line.amount = appointment_doc.paid_amount
+
+ sales_invoice.set_missing_values(for_validate = True)
+
+ sales_invoice.save(ignore_permissions=True)
+ sales_invoice.submit()
+ frappe.msgprint(_("Sales Invoice {0} created as paid".format(sales_invoice.name)), alert=True)
def appointment_cancel(appointment_id):
appointment = frappe.get_doc("Patient Appointment", appointment_id)
-
- # If invoice --> fee_validity update with -1 visit
- if appointment.sales_invoice:
- validity = frappe.db.exists({"doctype": "Fee Validity", "ref_invoice": appointment.sales_invoice})
- if validity:
- fee_validity = frappe.get_doc("Fee Validity", validity[0][0])
- visited = fee_validity.visited - 1
- frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
- if visited <= 0:
- frappe.msgprint(
- _("Appointment cancelled, Please review and cancel the invoice {0}".format(appointment.sales_invoice))
- )
+ # If invoiced --> fee_validity update with -1 visit
+ if appointment.invoiced:
+ sales_invoice = exists_sales_invoice(appointment)
+ if sales_invoice and cancel_sales_invoice(sales_invoice):
+ frappe.msgprint(
+ _("Appointment {0} and Sales Invoice {1} cancelled".format(appointment.name, sales_invoice.name))
+ )
+ else:
+ validity = validity_exists(appointment.practitioner, appointment.patient)
+ if validity:
+ fee_validity = frappe.get_doc("Fee Validity", validity[0][0])
+ if appointment_valid_in_fee_validity(appointment, fee_validity.valid_till, True, fee_validity.ref_invoice):
+ visited = fee_validity.visited - 1
+ frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
+ frappe.msgprint(
+ _("Appointment cancelled, Please review and cancel the invoice {0}".format(fee_validity.ref_invoice))
+ )
+ else:
+ frappe.msgprint(_("Appointment cancelled"))
else:
frappe.msgprint(_("Appointment cancelled"))
+ else:
+ frappe.msgprint(_("Appointment cancelled"))
+def appointment_valid_in_fee_validity(appointment, valid_end_date, invoiced, ref_invoice):
+ valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days")
+ max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit")
+ valid_start_date = add_days(getdate(valid_end_date), -int(valid_days))
+
+ # Appointments which has same fee validity range with the appointment
+ appointments = frappe.get_list("Patient Appointment",{'patient': appointment.patient, 'invoiced': invoiced,
+ 'appointment_date':("<=", getdate(valid_end_date)), 'appointment_date':(">=", getdate(valid_start_date)),
+ 'practitioner': appointment.practitioner}, order_by="appointment_date desc", limit=int(max_visit))
+
+ if appointments and len(appointments) > 0:
+ appointment_obj = appointments[len(appointments)-1]
+ sales_invoice = exists_sales_invoice(appointment_obj)
+ if sales_invoice.name == ref_invoice:
+ return True
+ return False
+
+def cancel_sales_invoice(sales_invoice):
+ if frappe.db.get_value("Healthcare Settings", None, "manage_appointment_invoice_automatically") == '1':
+ if len(sales_invoice.items) == 1:
+ sales_invoice.cancel()
+ return True
+ return False
+
+def exists_sales_invoice_item(appointment):
+ return frappe.db.exists(
+ "Sales Invoice Item",
+ {
+ "reference_dt": "Patient Appointment",
+ "reference_dn": appointment.name
+ }
+ )
+
+def exists_sales_invoice(appointment):
+ sales_item_exist = exists_sales_invoice_item(appointment)
+ if sales_item_exist:
+ sales_invoice = frappe.get_doc("Sales Invoice", frappe.db.get_value("Sales Invoice Item", sales_item_exist, "parent"))
+ return sales_invoice
+ return False
@frappe.whitelist()
def get_availability_data(date, practitioner):
@@ -197,100 +280,6 @@
message = frappe.db.get_value("Healthcare Settings", None, "app_con_msg")
send_message(doc, message)
-
-@frappe.whitelist()
-def invoice_appointment(appointment_doc):
- if not appointment_doc.name:
- return False
- sales_invoice = frappe.new_doc("Sales Invoice")
- sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer")
- sales_invoice.appointment = appointment_doc.name
- sales_invoice.due_date = getdate()
- sales_invoice.is_pos = '0'
- sales_invoice.company = appointment_doc.company
- sales_invoice.debit_to = get_receivable_account(appointment_doc.company)
-
- fee_validity = get_fee_validity(appointment_doc.practitioner, appointment_doc.patient, appointment_doc.appointment_date)
- procedure_template = False
- if appointment_doc.procedure_template:
- procedure_template = appointment_doc.procedure_template
- create_invoice_items(appointment_doc.practitioner, appointment_doc.company, sales_invoice, procedure_template)
-
- sales_invoice.save(ignore_permissions=True)
- frappe.db.sql("""update `tabPatient Appointment` set sales_invoice=%s where name=%s""", (sales_invoice.name, appointment_doc.name))
- frappe.db.set_value("Fee Validity", fee_validity.name, "ref_invoice", sales_invoice.name)
- encounter = frappe.db.exists({
- "doctype": "Patient Encounter",
- "appointment": appointment_doc.name})
- if encounter:
- frappe.db.set_value("Patient Encounter", encounter[0][0], "invoice", sales_invoice.name)
- return sales_invoice.name
-
-
-def get_fee_validity(practitioner, patient, date):
- validity_exist = validity_exists(practitioner, patient)
- if validity_exist:
- fee_validity = frappe.get_doc("Fee Validity", validity_exist[0][0])
- fee_validity = update_fee_validity(fee_validity, date)
- else:
- fee_validity = create_fee_validity(practitioner, patient, date)
- return fee_validity
-
-
-def validity_exists(practitioner, patient):
- return frappe.db.exists({
- "doctype": "Fee Validity",
- "practitioner": practitioner,
- "patient": patient})
-
-
-def update_fee_validity(fee_validity, date):
- max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit")
- valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days")
- if not valid_days:
- valid_days = 1
- if not max_visit:
- max_visit = 1
- date = getdate(date)
- valid_till = date + datetime.timedelta(days=int(valid_days))
- fee_validity.max_visit = max_visit
- fee_validity.visited = 1
- fee_validity.valid_till = valid_till
- fee_validity.save(ignore_permissions=True)
- return fee_validity
-
-
-def create_fee_validity(practitioner, patient, date):
- fee_validity = frappe.new_doc("Fee Validity")
- fee_validity.practitioner = practitioner
- fee_validity.patient = patient
- fee_validity = update_fee_validity(fee_validity, date)
- return fee_validity
-
-
-def create_invoice_items(practitioner, company, invoice, procedure_template):
- item_line = invoice.append("items")
- if procedure_template:
- procedure_template_obj = frappe.get_doc("Clinical Procedure Template", procedure_template)
- item_line.item_code = procedure_template_obj.item_code
- item_line.item_name = procedure_template_obj.template
- item_line.description = procedure_template_obj.description
- else:
- item_line.item_name = "Consulting Charges"
- item_line.description = "Consulting Charges: " + practitioner
- item_line.uom = "Nos"
- item_line.conversion_factor = 1
- item_line.income_account = get_income_account(practitioner, company)
- op_consulting_charge = frappe.db.get_value("Healthcare Practitioner", practitioner, "op_consulting_charge")
- if op_consulting_charge:
- item_line.rate = op_consulting_charge
- item_line.amount = op_consulting_charge
- item_line.qty = 1
-
-
- return invoice
-
-
@frappe.whitelist()
def create_encounter(appointment):
appointment = frappe.get_doc("Patient Appointment", appointment)
@@ -301,8 +290,8 @@
encounter.visit_department = appointment.department
encounter.patient_sex = appointment.patient_sex
encounter.encounter_date = appointment.appointment_date
- if appointment.sales_invoice:
- encounter.invoice = appointment.sales_invoice
+ if appointment.invoiced:
+ encounter.invoiced = True
return encounter.as_dict()
@@ -359,6 +348,7 @@
item.appointment_datetime = item.appointment_date + datetime.timedelta(minutes = item.duration)
return data
+
@frappe.whitelist()
def get_procedure_prescribed(patient):
return frappe.db.sql("""select pp.name, pp.procedure, pp.parent, ct.practitioner,
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py
index f9ef1cb..a030f19 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py
@@ -10,10 +10,6 @@
{
'label': _('Consultations'),
'items': ['Patient Encounter', 'Vital Signs', 'Patient Medical Record']
- },
- {
- 'label': _('Billing'),
- 'items': ['Sales Invoice']
}
]
}
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
index 47c9cad..2dd3512 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
@@ -94,11 +94,6 @@
}
};
});
- if(!frm.doc.__islocal && !frm.doc.invoice && (frappe.user.has_role("Accounts User"))){
- frm.add_custom_button(__('Invoice'), function() {
- btn_invoice_encounter(frm);
- },__("Create"));
- }
frm.set_df_property("appointment", "read_only", frm.doc.__islocal ? 0:1);
frm.set_df_property("patient", "read_only", frm.doc.__islocal ? 0:1);
frm.set_df_property("patient_age", "read_only", frm.doc.__islocal ? 0:1);
@@ -139,23 +134,6 @@
});
};
-var btn_invoice_encounter = function(frm){
- var doc = frm.doc;
- frappe.call({
- method:
- "erpnext.healthcare.doctype.encounter.encounter.create_invoice",
- args: {company: doc.company, patient: doc.patient, practitioner: doc.practitioner, encounter_id: doc.name },
- callback: function(data){
- if(!data.exc){
- if(data.message){
- frappe.set_route("Form", "Sales Invoice", data.message);
- }
- cur_frm.reload_doc();
- }
- }
- });
-};
-
var create_medical_record = function (frm) {
if(!frm.doc.patient){
frappe.throw(__("Please select patient"));
@@ -203,10 +181,16 @@
frappe.model.set_value(frm.doctype,frm.docname, "patient", data.message.patient);
frappe.model.set_value(frm.doctype,frm.docname, "type", data.message.appointment_type);
frappe.model.set_value(frm.doctype,frm.docname, "practitioner", data.message.practitioner);
- frappe.model.set_value(frm.doctype,frm.docname, "invoice", data.message.sales_invoice);
+ frappe.model.set_value(frm.doctype,frm.docname, "invoiced", data.message.invoiced);
}
});
}
+ else{
+ frappe.model.set_value(frm.doctype,frm.docname, "patient", "");
+ frappe.model.set_value(frm.doctype,frm.docname, "type", "");
+ frappe.model.set_value(frm.doctype,frm.docname, "practitioner", "");
+ frappe.model.set_value(frm.doctype,frm.docname, "invoiced", 0);
+ }
});
frappe.ui.form.on("Patient Encounter", "practitioner", function(frm) {
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
index d11d1a7..6f00e5b 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
@@ -220,9 +220,43 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "default": "",
+ "fetch_from": "patient.patient_name",
+ "fieldname": "patient_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": "Patient Name",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "patient_age",
"fieldtype": "Data",
- "hidden": 1,
+ "hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -235,9 +269,9 @@
"options": "",
"permlevel": 0,
"precision": "",
- "print_hide": 1,
+ "print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 1,
"reqd": 0,
@@ -255,7 +289,7 @@
"columns": 0,
"fieldname": "patient_sex",
"fieldtype": "Select",
- "hidden": 1,
+ "hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -268,9 +302,9 @@
"options": "\nMale\nFemale\nOther",
"permlevel": 0,
"precision": "",
- "print_hide": 1,
+ "print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 1,
"reqd": 0,
@@ -286,39 +320,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "practitioner",
- "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": 1,
- "label": "Healthcare Practitioner",
- "length": 0,
- "no_copy": 0,
- "options": "Healthcare Practitioner",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 1,
@@ -383,6 +384,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "practitioner",
+ "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": 1,
+ "label": "Healthcare Practitioner",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Healthcare Practitioner",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "visit_department",
"fieldtype": "Link",
"hidden": 0,
@@ -482,8 +516,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "invoice",
- "fieldtype": "Link",
+ "default": "0",
+ "fieldname": "invoiced",
+ "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -491,10 +526,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Invoice",
+ "label": "Invoiced",
"length": 0,
"no_copy": 1,
- "options": "Sales Invoice",
+ "options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -1145,4 +1180,4 @@
"track_changes": 1,
"track_seen": 1,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
index 3d8f952..4c62e57 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
@@ -5,9 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
-from frappe.utils import getdate, cstr
-import json
-from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
+from frappe.utils import cstr
class PatientEncounter(Document):
def on_update(self):
@@ -23,77 +21,6 @@
frappe.db.set_value("Patient Appointment", self.appointment, "status", "Open")
delete_medical_record(self)
-def set_sales_invoice_fields(company, patient):
- sales_invoice = frappe.new_doc("Sales Invoice")
- sales_invoice.customer = frappe.get_value("Patient", patient, "customer")
- # patient is custom field in sales inv.
- sales_invoice.due_date = getdate()
- sales_invoice.is_pos = '0'
- sales_invoice.debit_to = get_receivable_account(company)
-
- return sales_invoice
-
-def create_sales_invoice_item_lines(item, sales_invoice):
- sales_invoice_line = sales_invoice.append("items")
- sales_invoice_line.item_code = item.item_code
- sales_invoice_line.item_name = item.item_name
- sales_invoice_line.qty = 1.0
- sales_invoice_line.description = item.description
- return sales_invoice_line
-
-@frappe.whitelist()
-def create_drug_invoice(company, patient, prescriptions):
- list_ids = json.loads(prescriptions)
- if not (company or patient or prescriptions):
- return False
-
- sales_invoice = set_sales_invoice_fields(company, patient)
- sales_invoice.update_stock = 1
-
- for line_id in list_ids:
- line_obj = frappe.get_doc("Drug Prescription", line_id)
- if line_obj:
- if(line_obj.drug_code):
- item = frappe.get_doc("Item", line_obj.drug_code)
- sales_invoice_line = create_sales_invoice_item_lines(item, sales_invoice)
- sales_invoice_line.qty = line_obj.get_quantity()
- #income_account and cost_center in itemlines - by set_missing_values()
- sales_invoice.set_missing_values()
- return sales_invoice.as_dict()
-
-@frappe.whitelist()
-def create_invoice(company, patient, practitioner, encounter_id):
- if not encounter_id:
- return False
- sales_invoice = frappe.new_doc("Sales Invoice")
- sales_invoice.customer = frappe.get_value("Patient", patient, "customer")
- sales_invoice.due_date = getdate()
- sales_invoice.is_pos = '0'
- sales_invoice.debit_to = get_receivable_account(company)
-
- create_invoice_items(practitioner, sales_invoice, company)
-
- sales_invoice.save(ignore_permissions=True)
- frappe.db.sql("""update `tabPatient Encounter` set invoice=%s where name=%s""", (sales_invoice.name, encounter_id))
- appointment = frappe.db.get_value("Patient Encounter", encounter_id, "appointment")
- if appointment:
- frappe.db.set_value("Patient Appointment", appointment, "sales_invoice", sales_invoice.name)
- return sales_invoice.name
-
-def create_invoice_items(practitioner, invoice, company):
- item_line = invoice.append("items")
- item_line.item_name = "Consulting Charges"
- item_line.description = "Consulting Charges: " + practitioner
- item_line.qty = 1
- item_line.uom = "Nos"
- item_line.conversion_factor = 1
- item_line.income_account = get_income_account(practitioner, company)
- op_consulting_charge = frappe.get_value("Healthcare Practitioner", practitioner, "op_consulting_charge")
- if op_consulting_charge:
- item_line.rate = op_consulting_charge
- item_line.amount = op_consulting_charge
- return invoice
-
def insert_encounter_to_medical_record(doc):
subject = set_subject_field(doc)
medical_record = frappe.new_doc("Patient Medical Record")
diff --git a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json b/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json
index 8c35ccd..c6a6b44 100644
--- a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json
+++ b/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json
@@ -331,7 +331,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
- "search_index": 0,
+ "search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
@@ -454,4 +454,4 @@
"track_changes": 1,
"track_seen": 1,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.json b/erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.json
index b4c4532..d67da97 100644
--- a/erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.json
+++ b/erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.json
@@ -236,7 +236,72 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
- "search_index": 0,
+ "search_index": 1,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "procedure_created",
+ "fieldtype": "Check",
+ "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": "Procedure Created",
+ "length": 0,
+ "no_copy": 1,
+ "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": 1,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "fieldname": "invoiced",
+ "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": "Invoiced",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
@@ -252,7 +317,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-07-16 13:08:15.499491",
+ "modified": "2018-08-06 16:53:36.440428",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Procedure Prescription",
@@ -266,5 +331,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json
index 7655685..783fc3d 100644
--- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json
+++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.json
@@ -88,8 +88,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "invoice",
- "fieldtype": "Link",
+ "fieldname": "invoiced",
+ "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -97,10 +97,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Invoice",
+ "label": "Invoiced",
"length": 0,
- "no_copy": 0,
- "options": "Sales Invoice",
+ "no_copy": 1,
+ "options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -684,4 +684,4 @@
"track_changes": 1,
"track_seen": 1,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/healthcare/page/appointment_analytic/appointment_analytic.json b/erpnext/healthcare/page/appointment_analytic/appointment_analytic.json
index 4deff80..ac5ca1a 100644
--- a/erpnext/healthcare/page/appointment_analytic/appointment_analytic.json
+++ b/erpnext/healthcare/page/appointment_analytic/appointment_analytic.json
@@ -1,22 +1,24 @@
{
- "content": null,
- "creation": "2016-08-18 12:29:52.497819",
- "docstatus": 0,
- "doctype": "Page",
- "idx": 0,
- "modified": "2016-08-18 12:29:52.497819",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "appointment-analytic",
- "owner": "Administrator",
- "page_name": "Appointment Analytics",
+ "content": null,
+ "creation": "2016-08-18 12:29:52.497819",
+ "docstatus": 0,
+ "doctype": "Page",
+ "idx": 0,
+ "modified": "2018-08-06 11:40:53.082863",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "appointment-analytic",
+ "owner": "Administrator",
+ "page_name": "Appointment Analytics",
+ "restrict_to_domain": "Healthcare",
"roles": [
{
"role": "Physician"
}
- ],
- "script": null,
- "standard": "Yes",
- "style": null,
+ ],
+ "script": null,
+ "standard": "Yes",
+ "style": null,
+ "system_page": 0,
"title": "Appointment Analytics"
-}
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/page/medical_record/medical_record.json b/erpnext/healthcare/page/medical_record/medical_record.json
index 7c786ca..ca30c3b 100644
--- a/erpnext/healthcare/page/medical_record/medical_record.json
+++ b/erpnext/healthcare/page/medical_record/medical_record.json
@@ -1,23 +1,25 @@
{
- "content": null,
- "creation": "2016-06-09 11:33:14.025787",
- "docstatus": 0,
- "doctype": "Page",
- "icon": "icon-play",
- "idx": 0,
- "modified": "2017-03-06 11:20:40.174661",
- "modified_by": "Administrator",
- "module": "Healthcare",
- "name": "medical_record",
- "owner": "Administrator",
- "page_name": "medical_record",
+ "content": null,
+ "creation": "2016-06-09 11:33:14.025787",
+ "docstatus": 0,
+ "doctype": "Page",
+ "icon": "icon-play",
+ "idx": 0,
+ "modified": "2018-08-06 11:40:39.705660",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "medical_record",
+ "owner": "Administrator",
+ "page_name": "medical_record",
+ "restrict_to_domain": "Healthcare",
"roles": [
{
"role": "Physician"
}
- ],
- "script": null,
- "standard": "Yes",
- "style": null,
+ ],
+ "script": null,
+ "standard": "Yes",
+ "style": null,
+ "system_page": 0,
"title": "Medical Record"
-}
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json b/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json
index 2f85ff6..d3ad440 100644
--- a/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json
+++ b/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json
@@ -7,10 +7,10 @@
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
- "html": "<div >\n {% if letter_head and not no_letterhead -%}\n <div class=\"letter-head\">{{ letter_head }}</div>\n <hr>\n {%- endif %}\n\n {% if (doc.docstatus != 1) %}\n <b>Lab Tests have to be Submitted for Print .. !</b>\n {% elif (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"require_test_result_approval\") == '1' and doc.approval_status != \"Approved\") %}\n <b>Lab Tests have to be Approved for Print .. !</b>\n {%- else -%}\n <div class=\"row section-break\">\n <div class=\"col-xs-6 column-break\">\n {% if doc.invoice %}\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Order No.</label>\n </div>\n <div class=\"col-xs-7 value\">\n <strong>: </strong>{{doc.invoice}}\n </div>\n </div>\n {%- endif -%}\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Patient</label>\n </div>\n {% if doc.patient %}\n <div class=\"col-xs-7 value\">\n <strong>: </strong>{{doc.patient}}\n </div>\n {% else %}\n <div class=\"col-xs-7 value\">\n <strong>: </strong><em>Patient Name</em>\n </div>\n {%- endif -%}\n </div>\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Age</label>\n </div>\n <div class=\"col-xs-7 value\">\n <strong>: </strong> {{doc.patient_age}}\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Gender</label>\n </div>\n <div class=\"col-xs-7 value\">\n <strong>: </strong> {{doc.patient_sex}}\n </div>\n </div>\n\n </div>\n\n <div class=\"col-xs-6 column-break\">\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Healthcare Practitioner</label>\n </div>\n {% if doc.practitioner %}\n <div class=\"col-xs-7 text-left value\">\n <strong>: </strong>{{doc.practitioner}}\n </div>\n {%- endif -%}\n </div>\n\n {% if doc.sample_date %}\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Sample Date</label>\n </div>\n <div class=\"col-xs-7 text-left value\">\n <strong>: </strong>{{doc.sample_date}}\n </div>\n </div>\n {%- endif -%}\n\n {% if doc.result_date %}\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Result Date</label>\n </div>\n <div class=\"col-xs-7 text-left value\">\n <strong>: </strong>{{doc.result_date}}\n </div>\n </div>\n {%- endif -%}\n\n </div>\n\n </div>\n\n <div align=\"center\">\n <hr><h4 class=\"text-uppercase\"><b><u>Department of {{doc.department}}</u></b></h4>\n </div>\n\n <table class=\"table\">\n <tbody>\n {%- if doc.normal_test_items -%}\n <tr>\n <th>Name of Test</th>\n <th class=\"text-left\">Result</th>\n <th class=\"text-right\">Normal Range</th>\n </tr>\n\n {%- if doc.normal_test_items|length > 1 %}\n <tr><td style=\"width: 40%;\"> <b>{{ doc.test_name }}</b> </td><td></td></tr>\n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n <tr>\n <td style=\"width: 40%;border:none;\">\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.test_name -%}<b>{{ row.test_name }}</b>\n {%- else -%}   {%- endif -%}\n {%- if row.test_event -%}   {{ row.test_event }}{%- endif -%}\n </td>\n\n <td style=\"width: 20%;text-align: left;border:none;\">\n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%} \n {%- if row.test_uom -%}{{ row.test_uom }}{%- endif -%}\n </td>\n\n <td style=\"width: 30%;text-align: right;border:none;\">\n <div style=\"border: 0px;\">\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n </div>\n </td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n </tbody>\n </table>\n\n <table class=\"table\">\n <tbody>\n {%- if doc.special_test_items -%}\n <tr>\n <th>Name of Test</th>\n <th class=\"text-left\">Result</th>\n </tr>\n <tr><td style=\"width: 30%;border:none;\"> <b>{{ doc.test_name }}</b> </td><td></td></tr>\n {%- for row in doc.special_test_items -%}\n <tr>\n <td style=\"width: 30%;border:none;\">   {{ row.test_particulars }} </td>\n <td style=\"width: 70%;text-align: left;border:none;\">\n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%}\n </td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n\n {%- if doc.sensitivity_test_items -%}\n <tr>\n <th>Antibiotic</th>\n <th class=\"text-left\">Sensitivity</th>\n </tr>\n {%- for row in doc.sensitivity_test_items -%}\n <tr>\n <td style=\"width: 30%;border:none;\"> {{ row.antibiotic }} </td>\n <td style=\"width: 70%;text-align: left;border:none;\">{{ row.antibiotic_sensitivity }}</td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n\n </tbody>\n </table>\n {%- endif -%}\n\n <div align=\"right\">\n {%- if (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"employee_name_and_designation_in_print\") == '1') -%}\n <h6 class=\"text-uppercase\"><b>{{doc.employee_name}}</b></h6>\n <h6 class=\"text-uppercase\"><b>{{doc.employee_designation}}</b></h6>\n {%- else -%}\n <h6 ><b>{{frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") }}</b></h6>\n {%- endif -%}\n </div>\n</div>\n",
+ "html": "<div >\n {% if letter_head and not no_letterhead -%}\n <div class=\"letter-head\">{{ letter_head }}</div>\n <hr>\n {%- endif %}\n\n {% if (doc.docstatus != 1) %}\n <b>Lab Tests have to be Submitted for Print .. !</b>\n {% elif (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"require_test_result_approval\") == '1' and doc.approval_status != \"Approved\") %}\n <b>Lab Tests have to be Approved for Print .. !</b>\n {%- else -%}\n <div class=\"row section-break\">\n <div class=\"col-xs-6 column-break\">\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Patient</label>\n </div>\n {% if doc.patient %}\n <div class=\"col-xs-7 value\">\n <strong>: </strong>{{doc.patient}}\n </div>\n {% else %}\n <div class=\"col-xs-7 value\">\n <strong>: </strong><em>Patient Name</em>\n </div>\n {%- endif -%}\n </div>\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Age</label>\n </div>\n <div class=\"col-xs-7 value\">\n <strong>: </strong> {{doc.patient_age}}\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Gender</label>\n </div>\n <div class=\"col-xs-7 value\">\n <strong>: </strong> {{doc.patient_sex}}\n </div>\n </div>\n\n </div>\n\n <div class=\"col-xs-6 column-break\">\n\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Practitioner</label>\n </div>\n {% if doc.practitioner %}\n <div class=\"col-xs-7 text-left value\">\n <strong>: </strong>{{doc.practitioner}}\n </div>\n {%- endif -%}\n </div>\n\n {% if doc.sample_date %}\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Sample Date</label>\n </div>\n <div class=\"col-xs-7 text-left value\">\n <strong>: </strong>{{doc.sample_date}}\n </div>\n </div>\n {%- endif -%}\n\n {% if doc.result_date %}\n <div class=\"row\">\n <div class=\"col-xs-4 text-left\">\n <label>Result Date</label>\n </div>\n <div class=\"col-xs-7 text-left value\">\n <strong>: </strong>{{doc.result_date}}\n </div>\n </div>\n {%- endif -%}\n\n </div>\n\n </div>\n\n <div align=\"center\">\n <hr><h4 class=\"text-uppercase\"><b><u>Department of {{doc.department}}</u></b></h4>\n </div>\n\n <table class=\"table\">\n <tbody>\n {%- if doc.normal_test_items -%}\n <tr>\n <th>Name of Test</th>\n <th class=\"text-left\">Result</th>\n <th class=\"text-right\">Normal Range</th>\n </tr>\n\n {%- if doc.normal_test_items|length > 1 %}\n <tr><td style=\"width: 40%;\"> <b>{{ doc.test_name }}</b> </td><td></td></tr>\n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n <tr>\n <td style=\"width: 40%;border:none;\">\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.test_name -%}<b>{{ row.test_name }}</b>\n {%- else -%}   {%- endif -%}\n {%- if row.test_event -%}   {{ row.test_event }}{%- endif -%}\n </td>\n\n <td style=\"width: 20%;text-align: left;border:none;\">\n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%} \n {%- if row.test_uom -%}{{ row.test_uom }}{%- endif -%}\n </td>\n\n <td style=\"width: 30%;text-align: right;border:none;\">\n <div style=\"border: 0px;\">\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n </div>\n </td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n </tbody>\n </table>\n\n <table class=\"table\">\n <tbody>\n {%- if doc.special_test_items -%}\n <tr>\n <th>Name of Test</th>\n <th class=\"text-left\">Result</th>\n </tr>\n <tr><td style=\"width: 30%;border:none;\"> <b>{{ doc.test_name }}</b> </td><td></td></tr>\n {%- for row in doc.special_test_items -%}\n <tr>\n <td style=\"width: 30%;border:none;\">   {{ row.test_particulars }} </td>\n <td style=\"width: 70%;text-align: left;border:none;\">\n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%}\n </td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n\n {%- if doc.sensitivity_test_items -%}\n <tr>\n <th>Antibiotic</th>\n <th class=\"text-left\">Sensitivity</th>\n </tr>\n {%- for row in doc.sensitivity_test_items -%}\n <tr>\n <td style=\"width: 30%;border:none;\"> {{ row.antibiotic }} </td>\n <td style=\"width: 70%;text-align: left;border:none;\">{{ row.antibiotic_sensitivity }}</td>\n </tr>\n\n {%- endfor -%}\n {%- endif -%}\n\n </tbody>\n </table>\n {%- endif -%}\n\n <div align=\"right\">\n {%- if (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"employee_name_and_designation_in_print\") == '1') -%}\n <h6 class=\"text-uppercase\"><b>{{doc.employee_name}}</b></h6>\n <h6 class=\"text-uppercase\"><b>{{doc.employee_designation}}</b></h6>\n {%- else -%}\n <h6 ><b>{{frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") }}</b></h6>\n {%- endif -%}\n </div>\n</div>\n",
"idx": 0,
"line_breaks": 0,
- "modified": "2018-07-10 11:29:24.167265",
+ "modified": "2018-07-13 12:51:25.750441",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Test Print",
@@ -19,4 +19,4 @@
"print_format_type": "Server",
"show_section_headings": 0,
"standard": "Yes"
-}
\ No newline at end of file
+}
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 f133a8e..30e5a5f 100644
--- a/erpnext/healthcare/report/lab_test_report/lab_test_report.json
+++ b/erpnext/healthcare/report/lab_test_report/lab_test_report.json
@@ -1,17 +1,17 @@
{
"add_total_row": 1,
- "apply_user_permissions": 1,
"creation": "2013-04-23 18:15:29",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 1,
"is_standard": "Yes",
- "modified": "2017-08-23 14:54:12.593140",
+ "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",
@@ -20,7 +20,13 @@
"role": "Laboratory User"
},
{
- "role": "System Manager"
+ "role": "Nursing User"
+ },
+ {
+ "role": "LabTest Approver"
+ },
+ {
+ "role": "Healthcare Administrator"
}
]
}
\ No newline at end of file
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 e4771c5..b9a26df 100644
--- a/erpnext/healthcare/report/lab_test_report/lab_test_report.py
+++ b/erpnext/healthcare/report/lab_test_report/lab_test_report.py
@@ -17,7 +17,7 @@
data = []
for lab_test in lab_test_list:
- row = [ lab_test.test_name, lab_test.patient, lab_test.practitioner, lab_test.invoice, lab_test.status, lab_test.result_date, lab_test.department]
+ row = [ lab_test.test_name, lab_test.patient, lab_test.practitioner, lab_test.invoiced, lab_test.status, lab_test.result_date, lab_test.department]
data.append(row)
return columns, data
@@ -28,7 +28,7 @@
_("Test") + ":Data:120",
_("Patient") + ":Link/Patient:180",
_("Healthcare Practitioner") + ":Link/Healthcare Practitioner:120",
- _("Invoice") + ":Link/Sales Invoice:120",
+ _("Invoiced") + ":Check:100",
_("Status") + ":Data:120",
_("Result Date") + ":Date:120",
_("Department") + ":Data:120",
@@ -52,7 +52,7 @@
def get_lab_test(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""select name, patient, test_name, patient_name, status, result_date, practitioner, invoice, department
+ return frappe.db.sql("""select name, patient, 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)
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
new file mode 100644
index 0000000..1be82e2
--- /dev/null
+++ b/erpnext/healthcare/utils.py
@@ -0,0 +1,426 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, earthians and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import datetime
+from frappe import _
+import math
+from frappe.utils import time_diff_in_hours, rounded, getdate, add_days
+from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
+from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity, update_fee_validity
+from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
+
+@frappe.whitelist()
+def get_healthcare_services_to_invoice(patient):
+ patient = frappe.get_doc("Patient", patient)
+ if patient:
+ if patient.customer:
+ item_to_invoice = []
+ patient_appointments = frappe.get_list("Patient Appointment",{'patient': patient.name, 'invoiced': False},
+ order_by="appointment_date")
+ if patient_appointments:
+ fee_validity_details = []
+ valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days")
+ max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit")
+ for patient_appointment in patient_appointments:
+ patient_appointment_obj = frappe.get_doc("Patient Appointment", patient_appointment['name'])
+
+ if patient_appointment_obj.procedure_template:
+ if frappe.db.get_value("Clinical Procedure Template", patient_appointment_obj.procedure_template, "is_billable") == 1:
+ item_to_invoice.append({'reference_type': 'Patient Appointment', 'reference_name': patient_appointment_obj.name, 'service': patient_appointment_obj.procedure_template})
+ else:
+ practitioner_exist_in_list = False
+ skip_invoice = False
+ if fee_validity_details:
+ for validity in fee_validity_details:
+ if validity['practitioner'] == patient_appointment_obj.practitioner:
+ practitioner_exist_in_list = True
+ if validity['valid_till'] >= patient_appointment_obj.appointment_date:
+ validity['visits'] = validity['visits']+1
+ if int(max_visit) > validity['visits']:
+ skip_invoice = True
+ if not skip_invoice:
+ validity['visits'] = 1
+ validity['valid_till'] = patient_appointment_obj.appointment_date + datetime.timedelta(days=int(valid_days))
+ if not practitioner_exist_in_list:
+ valid_till = patient_appointment_obj.appointment_date + datetime.timedelta(days=int(valid_days))
+ visits = 0
+ validity_exist = validity_exists(patient_appointment_obj.practitioner, patient_appointment_obj.patient)
+ if validity_exist:
+ fee_validity = frappe.get_doc("Fee Validity", validity_exist[0][0])
+ valid_till = fee_validity.valid_till
+ visits = fee_validity.visited
+ fee_validity_details.append({'practitioner': patient_appointment_obj.practitioner,
+ 'valid_till': valid_till, 'visits': visits})
+
+ if not skip_invoice:
+ practitioner_charge = 0
+ income_account = None
+ service_item = None
+ if patient_appointment_obj.practitioner:
+ service_item, practitioner_charge = service_item_and_practitioner_charge(patient_appointment_obj)
+ income_account = get_income_account(patient_appointment_obj.practitioner, patient_appointment_obj.company)
+ item_to_invoice.append({'reference_type': 'Patient Appointment', 'reference_name': patient_appointment_obj.name,
+ 'service': service_item, 'rate': practitioner_charge,
+ 'income_account': income_account})
+
+ encounters = frappe.get_list("Patient Encounter", {'patient': patient.name, 'invoiced': False, 'docstatus': 1})
+ if encounters:
+ for encounter in encounters:
+ encounter_obj = frappe.get_doc("Patient Encounter", encounter['name'])
+ if not encounter_obj.appointment:
+ practitioner_charge = 0
+ income_account = None
+ service_item = None
+ if encounter_obj.practitioner:
+ service_item, practitioner_charge = service_item_and_practitioner_charge(encounter_obj)
+ income_account = get_income_account(encounter_obj.practitioner, encounter_obj.company)
+
+ item_to_invoice.append({'reference_type': 'Patient Encounter', 'reference_name': encounter_obj.name,
+ 'service': service_item, 'rate': practitioner_charge,
+ 'income_account': income_account})
+
+ lab_tests = frappe.get_list("Lab Test", {'patient': patient.name, 'invoiced': False})
+ if lab_tests:
+ for lab_test in lab_tests:
+ lab_test_obj = frappe.get_doc("Lab Test", lab_test['name'])
+ if frappe.db.get_value("Lab Test Template", lab_test_obj.template, "is_billable") == 1:
+ item_to_invoice.append({'reference_type': 'Lab Test', 'reference_name': lab_test_obj.name,
+ 'service': frappe.db.get_value("Lab Test Template", lab_test_obj.template, "item")})
+
+ lab_rxs = frappe.db.sql("""select lp.name from `tabPatient Encounter` et, `tabLab Prescription` lp
+ where et.patient=%s and lp.parent=et.name and lp.test_created=0 and lp.invoiced=0""", (patient.name))
+ if lab_rxs:
+ for lab_rx in lab_rxs:
+ rx_obj = frappe.get_doc("Lab Prescription", lab_rx[0])
+ if rx_obj.test_code and (frappe.db.get_value("Lab Test Template", rx_obj.test_code, "is_billable") == 1):
+ item_to_invoice.append({'reference_type': 'Lab Prescription', 'reference_name': rx_obj.name,
+ 'service': frappe.db.get_value("Lab Test Template", rx_obj.test_code, "item")})
+
+ procedures = frappe.get_list("Clinical Procedure", {'patient': patient.name, 'invoiced': False})
+ if procedures:
+ for procedure in procedures:
+ procedure_obj = frappe.get_doc("Clinical Procedure", procedure['name'])
+ if not procedure_obj.appointment:
+ if procedure_obj.procedure_template and (frappe.db.get_value("Clinical Procedure Template", procedure_obj.procedure_template, "is_billable") == 1):
+ item_to_invoice.append({'reference_type': 'Clinical Procedure', 'reference_name': procedure_obj.name,
+ 'service': frappe.db.get_value("Clinical Procedure Template", procedure_obj.procedure_template, "item")})
+
+ procedure_rxs = frappe.db.sql("""select pp.name from `tabPatient Encounter` et,
+ `tabProcedure Prescription` pp where et.patient=%s and pp.parent=et.name and
+ pp.procedure_created=0 and pp.invoiced=0 and pp.appointment_booked=0""", (patient.name))
+ if procedure_rxs:
+ for procedure_rx in procedure_rxs:
+ rx_obj = frappe.get_doc("Procedure Prescription", procedure_rx[0])
+ if frappe.db.get_value("Clinical Procedure Template", rx_obj.procedure, "is_billable") == 1:
+ item_to_invoice.append({'reference_type': 'Procedure Prescription', 'reference_name': rx_obj.name,
+ 'service': frappe.db.get_value("Clinical Procedure Template", rx_obj.procedure, "item")})
+
+ procedures = frappe.get_list("Clinical Procedure",
+ {'patient': patient.name, 'invoice_separately_as_consumables': True, 'consumption_invoiced': False,
+ 'consume_stock': True, 'status': 'Completed'})
+ if procedures:
+ service_item = get_healthcare_service_item('clinical_procedure_consumable_item')
+ if not service_item:
+ msg = _(("Please Configure {0} in ").format("Clinical Procedure Consumable Item") \
+ + """<b><a href="#Form/Healthcare Settings">Healthcare Settings</a></b>""")
+ frappe.throw(msg)
+ for procedure in procedures:
+ procedure_obj = frappe.get_doc("Clinical Procedure", procedure['name'])
+ item_to_invoice.append({'reference_type': 'Clinical Procedure', 'reference_name': procedure_obj.name,
+ 'service': service_item, 'rate': procedure_obj.consumable_total_amount, 'description': procedure_obj.consumption_details})
+
+ inpatient_services = frappe.db.sql("""select io.name, io.parent from `tabInpatient Record` ip,
+ `tabInpatient Occupancy` io where ip.patient=%s and io.parent=ip.name and
+ io.left=1 and io.invoiced=0""", (patient.name))
+ if inpatient_services:
+ for inpatient_service in inpatient_services:
+ inpatient_occupancy = frappe.get_doc("Inpatient Occupancy", inpatient_service[0])
+ service_unit_type = frappe.get_doc("Healthcare Service Unit Type", frappe.db.get_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "service_unit_type"))
+ if service_unit_type and service_unit_type.is_billable == 1:
+ hours_occupied = time_diff_in_hours(inpatient_occupancy.check_out, inpatient_occupancy.check_in)
+ qty = 0.5
+ if hours_occupied > 0:
+ actual_qty = hours_occupied / service_unit_type.no_of_hours
+ floor = math.floor(actual_qty)
+ decimal_part = actual_qty - floor
+ if decimal_part > 0.5:
+ qty = rounded(floor + 1, 1)
+ elif decimal_part < 0.5 and decimal_part > 0:
+ qty = rounded(floor + 0.5, 1)
+ if qty <= 0:
+ qty = 0.5
+ item_to_invoice.append({'reference_type': 'Inpatient Occupancy', 'reference_name': inpatient_occupancy.name,
+ 'service': service_unit_type.item, 'qty': qty})
+
+ return item_to_invoice
+ else:
+ frappe.throw(_("The Patient {0} do not have customer refrence to invoice").format(patient.name))
+
+def service_item_and_practitioner_charge(doc):
+ is_ip = doc_is_ip(doc)
+ if is_ip:
+ service_item = get_practitioner_service_item(doc.practitioner, "inpatient_visit_charge_item")
+ if not service_item:
+ service_item = get_healthcare_service_item("inpatient_visit_charge_item")
+ else:
+ service_item = get_practitioner_service_item(doc.practitioner, "op_consulting_charge_item")
+ if not service_item:
+ service_item = get_healthcare_service_item("op_consulting_charge_item")
+ if not service_item:
+ throw_config_service_item(is_ip)
+
+ practitioner_charge = get_practitioner_charge(doc.practitioner, is_ip)
+ if not practitioner_charge:
+ throw_config_practitioner_charge(is_ip, doc.practitioner)
+
+ return service_item, practitioner_charge
+
+def throw_config_service_item(is_ip):
+ service_item_lable = "Out Patient Consulting Charge Item"
+ if is_ip:
+ service_item_lable = "Inpatient Visit Charge Item"
+
+ msg = _(("Please Configure {0} in ").format(service_item_lable) \
+ + """<b><a href="#Form/Healthcare Settings">Healthcare Settings</a></b>""")
+ frappe.throw(msg)
+
+def throw_config_practitioner_charge(is_ip, practitioner):
+ charge_name = "OP Consulting Charge"
+ if is_ip:
+ charge_name = "Inpatient Visit Charge"
+
+ msg = _(("Please Configure {0} for Healthcare Practitioner").format(charge_name) \
+ + """ <b><a href="#Form/Healthcare Practitioner/{0}">{0}</a></b>""".format(practitioner))
+ frappe.throw(msg)
+
+def get_practitioner_service_item(practitioner, service_item_field):
+ return frappe.db.get_value("Healthcare Practitioner", practitioner, service_item_field)
+
+def get_healthcare_service_item(service_item_field):
+ return frappe.db.get_value("Healthcare Settings", None, service_item_field)
+
+def doc_is_ip(doc):
+ is_ip = False
+ if doc.inpatient_record:
+ is_ip = True
+ return is_ip
+
+def get_practitioner_charge(practitioner, is_ip):
+ if is_ip:
+ practitioner_charge = frappe.db.get_value("Healthcare Practitioner", practitioner, "inpatient_visit_charge")
+ else:
+ practitioner_charge = frappe.db.get_value("Healthcare Practitioner", practitioner, "op_consulting_charge")
+ if practitioner_charge:
+ return practitioner_charge
+ return False
+
+def manage_invoice_submit_cancel(doc, method):
+ if doc.items:
+ for item in doc.items:
+ if item.reference_dt and item.reference_dn:
+ if frappe.get_meta(item.reference_dt).has_field("invoiced"):
+ set_invoiced(item, method, doc.name)
+
+ if method=="on_submit" and frappe.db.get_value("Healthcare Settings", None, "create_test_on_si_submit") == '1':
+ create_multiple("Sales Invoice", doc.name)
+
+def set_invoiced(item, method, ref_invoice=None):
+ invoiced = False
+ if(method=="on_submit"):
+ validate_invoiced_on_submit(item)
+ invoiced = True
+
+ if item.reference_dt == 'Clinical Procedure':
+ if get_healthcare_service_item('clinical_procedure_consumable_item') == item.item_code:
+ frappe.db.set_value(item.reference_dt, item.reference_dn, "consumption_invoiced", invoiced)
+ else:
+ frappe.db.set_value(item.reference_dt, item.reference_dn, "invoiced", invoiced)
+ else:
+ frappe.db.set_value(item.reference_dt, item.reference_dn, "invoiced", invoiced)
+
+ if item.reference_dt == 'Patient Appointment':
+ if frappe.db.get_value('Patient Appointment', item.reference_dn, 'procedure_template'):
+ dt_from_appointment = "Clinical Procedure"
+ else:
+ manage_fee_validity(item.reference_dn, method, ref_invoice)
+ dt_from_appointment = "Patient Encounter"
+ manage_doc_for_appoitnment(dt_from_appointment, item.reference_dn, invoiced)
+
+ elif item.reference_dt == 'Lab Prescription':
+ manage_prescriptions(invoiced, item.reference_dt, item.reference_dn, "Lab Test", "test_created")
+
+ elif item.reference_dt == 'Procedure Prescription':
+ manage_prescriptions(invoiced, item.reference_dt, item.reference_dn, "Clinical Procedure", "procedure_created")
+
+def validate_invoiced_on_submit(item):
+ if item.reference_dt == 'Clinical Procedure' and get_healthcare_service_item('clinical_procedure_consumable_item') == item.item_code:
+ is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, "consumption_invoiced")
+ else:
+ is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, "invoiced")
+ if is_invoiced == 1:
+ frappe.throw(_("The item referenced by {0} - {1} is already invoiced"\
+ ).format(item.reference_dt, item.reference_dn))
+
+def manage_prescriptions(invoiced, ref_dt, ref_dn, dt, created_check_field):
+ created = frappe.db.get_value(ref_dt, ref_dn, created_check_field)
+ if created == 1:
+ # Fetch the doc created for the prescription
+ doc_created = frappe.db.get_value(dt, {'prescription': ref_dn})
+ frappe.db.set_value(dt, doc_created, 'invoiced', invoiced)
+
+def validity_exists(practitioner, patient):
+ return frappe.db.exists({
+ "doctype": "Fee Validity",
+ "practitioner": practitioner,
+ "patient": patient})
+
+def manage_fee_validity(appointment_name, method, ref_invoice=None):
+ appointment_doc = frappe.get_doc("Patient Appointment", appointment_name)
+ validity_exist = validity_exists(appointment_doc.practitioner, appointment_doc.patient)
+ do_not_update = False
+ visited = 0
+ if validity_exist:
+ fee_validity = frappe.get_doc("Fee Validity", validity_exist[0][0])
+ # Check if the validity is valid
+ if (fee_validity.valid_till >= appointment_doc.appointment_date):
+ if (method == "on_cancel" and appointment_doc.status != "Closed"):
+ if ref_invoice == fee_validity.ref_invoice:
+ visited = fee_validity.visited - 1
+ if visited < 0:
+ visited = 0
+ frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
+ do_not_update = True
+ elif (method == "on_submit" and fee_validity.visited < fee_validity.max_visit):
+ visited = fee_validity.visited + 1
+ frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
+ do_not_update = True
+ else:
+ do_not_update = False
+
+ if not do_not_update:
+ fee_validity = update_fee_validity(fee_validity, appointment_doc.appointment_date, ref_invoice)
+ visited = fee_validity.visited
+ else:
+ fee_validity = create_fee_validity(appointment_doc.practitioner, appointment_doc.patient, appointment_doc.appointment_date, ref_invoice)
+ visited = fee_validity.visited
+
+ print("do_not_update: ", do_not_update)
+ print("visited: ", visited)
+
+ # Mark All Patient Appointment invoiced = True in the validity range do not cross the max visit
+ if (method == "on_cancel"):
+ invoiced = True
+ else:
+ invoiced = False
+
+ patient_appointments = appointments_valid_in_fee_validity(appointment_doc, invoiced)
+ if patient_appointments and fee_validity:
+ visit = visited
+ for appointment in patient_appointments:
+ if (method == "on_cancel" and appointment.status != "Closed"):
+ if ref_invoice == fee_validity.ref_invoice:
+ visited = visited - 1
+ if visited < 0:
+ visited = 0
+ frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
+ frappe.db.set_value("Patient Appointment", appointment.name, "invoiced", False)
+ manage_doc_for_appoitnment("Patient Encounter", appointment.name, False)
+ elif method == "on_submit" and int(fee_validity.max_visit) > visit:
+ if ref_invoice == fee_validity.ref_invoice:
+ visited = visited + 1
+ frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
+ frappe.db.set_value("Patient Appointment", appointment.name, "invoiced", True)
+ manage_doc_for_appoitnment("Patient Encounter", appointment.name, True)
+ if ref_invoice == fee_validity.ref_invoice:
+ visit = visit + 1
+
+ if method == "on_cancel":
+ ref_invoice_in_fee_validity = frappe.db.get_value("Fee Validity", fee_validity.name, 'ref_invoice')
+ if ref_invoice_in_fee_validity == ref_invoice:
+ frappe.delete_doc("Fee Validity", fee_validity.name)
+
+def appointments_valid_in_fee_validity(appointment, invoiced):
+ valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days")
+ max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit")
+ valid_days_date = add_days(getdate(appointment.appointment_date), int(valid_days))
+ return frappe.get_list("Patient Appointment",{'patient': appointment.patient, 'invoiced': invoiced,
+ 'appointment_date':("<=", valid_days_date), 'appointment_date':(">=", getdate(appointment.appointment_date)),
+ 'practitioner': appointment.practitioner}, order_by="appointment_date", limit=int(max_visit)-1)
+
+def manage_doc_for_appoitnment(dt_from_appointment, appointment, invoiced):
+ dn_from_appointment = frappe.db.exists(
+ dt_from_appointment,
+ {
+ "appointment": appointment
+ }
+ )
+ if dn_from_appointment:
+ frappe.db.set_value(dt_from_appointment, dn_from_appointment, "invoiced", invoiced)
+
+@frappe.whitelist()
+def get_drugs_to_invoice(encounter):
+ encounter = frappe.get_doc("Patient Encounter", encounter)
+ if encounter:
+ patient = frappe.get_doc("Patient", encounter.patient)
+ if patient and patient.customer:
+ item_to_invoice = []
+ for drug_line in encounter.drug_prescription:
+ if drug_line.drug_code:
+ qty = 1
+ if frappe.db.get_value("Item", drug_line.drug_code, "stock_uom") == "Nos":
+ qty = drug_line.get_quantity()
+ item_to_invoice.append({'drug_code': drug_line.drug_code, 'quantity': qty,
+ 'description': drug_line.dosage+" for "+drug_line.period})
+ return item_to_invoice
+
+@frappe.whitelist()
+def get_children(doctype, parent, company, is_root=False):
+ parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_')
+ fields = [
+ 'name as value',
+ 'is_group as expandable',
+ 'lft',
+ 'rgt'
+ ]
+ # fields = [ 'name', 'is_group', 'lft', 'rgt' ]
+ filters = [['ifnull(`{0}`,"")'.format(parent_fieldname), '=', '' if is_root else parent]]
+
+ if is_root:
+ fields += ['service_unit_type'] if doctype == 'Healthcare Service Unit' else []
+ filters.append(['company', '=', company])
+
+ else:
+ fields += ['service_unit_type', 'allow_appointments', 'inpatient_occupancy', 'occupancy_status'] if doctype == 'Healthcare Service Unit' else []
+ fields += [parent_fieldname + ' as parent']
+
+ hc_service_units = frappe.get_list(doctype, fields=fields, filters=filters)
+
+ if doctype == 'Healthcare Service Unit':
+ for each in hc_service_units:
+ occupancy_msg = ""
+ if each['expandable'] == 1:
+ occupied = False
+ vacant = False
+ child_list = frappe.db.sql("""
+ select name, occupancy_status from `tabHealthcare Service Unit`
+ where inpatient_occupancy = 1 and
+ lft > %s and rgt < %s""",
+ (each['lft'], each['rgt']))
+ for child in child_list:
+ print(child[0], child[1])
+ if not occupied:
+ occupied = 0
+ if child[1] == "Occupied":
+ occupied += 1
+ if not vacant:
+ vacant = 0
+ if child[1] == "Vacant":
+ vacant += 1
+ if vacant and occupied:
+ occupancy_total = vacant+occupied
+ occupancy_msg = str(occupied) + " Occupied out of " + str(occupancy_total)
+ each["occupied_out_of_vacant"] = occupancy_msg
+ return hc_service_units
diff --git a/erpnext/healthcare/web_form/lab_test/lab_test.json b/erpnext/healthcare/web_form/lab_test/lab_test.json
index 89029fa..f6f51b2 100644
--- a/erpnext/healthcare/web_form/lab_test/lab_test.json
+++ b/erpnext/healthcare/web_form/lab_test/lab_test.json
@@ -18,7 +18,7 @@
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
- "modified": "2018-07-16 13:10:47.940128",
+ "modified": "2018-07-17 13:10:47.940128",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "lab-test",
@@ -44,13 +44,14 @@
"reqd": 1
},
{
- "fieldname": "invoice",
- "fieldtype": "Link",
+ "default": "0",
+ "fieldname": "invoiced",
+ "fieldtype": "Check",
"hidden": 0,
- "label": "Invoice",
+ "label": "Invoiced",
"max_length": 0,
"max_value": 0,
- "options": "Sales Invoice",
+ "options": "",
"read_only": 0,
"reqd": 0
},
@@ -232,4 +233,4 @@
"reqd": 0
}
]
-}
\ No newline at end of file
+}
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index c706be9..a01011a 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -127,6 +127,23 @@
if(!r.exc) frm.refresh_fields();
}
});
+ },
+
+ routing: function(frm) {
+ if (frm.doc.routing) {
+ frappe.call({
+ doc: frm.doc,
+ method: "get_routing",
+ freeze: true,
+ callback: function(r) {
+ if (!r.exc) {
+ frm.refresh_fields();
+ erpnext.bom.calculate_op_cost(frm.doc);
+ erpnext.bom.calculate_total(frm.doc);
+ }
+ }
+ });
+ }
}
});
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 69a75c4..77fc498 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -479,6 +479,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "with_operations",
+ "fieldname": "transfer_material_against_job_card",
+ "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": "Transfer Material Against Job Card",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "currency_detail",
"fieldtype": "Section Break",
"hidden": 0,
@@ -674,6 +707,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "routing",
+ "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": "Routing",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Routing",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "operations",
"fieldtype": "Table",
"hidden": 0,
@@ -1877,7 +1943,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-06-01 03:45:06.731308",
+ "modified": "2018-07-15 11:09:19.425998",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
@@ -1930,5 +1996,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 98aee05..5e9f46c 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -89,6 +89,13 @@
return item
+ def get_routing(self):
+ if self.routing:
+ for d in frappe.get_all("BOM Operation", fields = ["*"],
+ filters = {'parenttype': 'Routing', 'parent': self.routing}):
+ child = self.append('operations', d)
+ child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
+
def validate_rm_item(self, item):
if (item[0]['name'] in [it.item_code for it in self.items]) and item[0]['name'] == self.item:
frappe.throw(_("BOM #{0}: Raw material cannot be same as main Item").format(self.name))
@@ -458,6 +465,7 @@
self.add_to_cur_exploded_items(frappe._dict({
'item_code' : d.item_code,
'item_name' : d.item_name,
+ 'operation' : d.operation,
'source_warehouse': d.source_warehouse,
'description' : d.description,
'image' : d.image,
@@ -480,7 +488,7 @@
""" Add all items from Flat BOM of child BOM"""
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
child_fb_items = frappe.db.sql("""select bom_item.item_code, bom_item.item_name,
- bom_item.description, bom_item.source_warehouse,
+ bom_item.description, bom_item.source_warehouse, bom_item.operation,
bom_item.stock_uom, bom_item.stock_qty, bom_item.rate, bom_item.allow_transfer_for_manufacture,
bom_item.stock_qty / ifnull(bom.quantity, 1) as qty_consumed_per_unit
from `tabBOM Explosion Item` bom_item, tabBOM bom
@@ -491,6 +499,7 @@
'item_code' : d['item_code'],
'item_name' : d['item_name'],
'source_warehouse' : d['source_warehouse'],
+ 'operation' : d['operation'],
'description' : d['description'],
'stock_uom' : d['stock_uom'],
'stock_qty' : d['qty_consumed_per_unit'] * stock_qty,
@@ -571,7 +580,7 @@
query = query.format(table="BOM Explosion Item",
where_conditions="",
is_stock_item=is_stock_item,
- select_columns = """, bom_item.source_warehouse, bom_item.allow_transfer_for_manufacture,
+ select_columns = """, bom_item.source_warehouse, bom_item.operation, bom_item.allow_transfer_for_manufacture,
(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s ) as idx""")
items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
@@ -580,7 +589,7 @@
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
else:
query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item,
- select_columns = ", bom_item.source_warehouse, bom_item.idx, bom_item.allow_transfer_for_manufacture")
+ select_columns = ", bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.allow_transfer_for_manufacture")
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
for item in items:
diff --git a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json
index 8c7db8f..ab3c5a1 100644
--- a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json
+++ b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json
@@ -54,37 +54,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "cb",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "item_name",
"fieldtype": "Data",
"hidden": 0,
@@ -117,6 +86,37 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "cb",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "source_warehouse",
"fieldtype": "Link",
"hidden": 0,
@@ -150,6 +150,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "operation",
+ "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": "Operation",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Operation",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"hidden": 0,
@@ -576,7 +609,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-07-12 16:29:55.464426",
+ "modified": "2018-08-27 16:32:35.152139",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Explosion Item",
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index ceee2c1..b31f692 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -17,6 +17,39 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "columns": 0,
+ "fieldname": "operation",
+ "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": "Operation",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Operation",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"columns": 3,
"fieldname": "item_code",
"fieldtype": "Link",
@@ -1000,7 +1033,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-07-12 16:16:16.815165",
+ "modified": "2018-08-22 16:16:16.815165",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",
diff --git a/erpnext/manufacturing/doctype/job_card/__init__.py b/erpnext/manufacturing/doctype/job_card/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card/__init__.py
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
new file mode 100644
index 0000000..6f5290e
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -0,0 +1,113 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Job Card', {
+ refresh: function(frm) {
+ if (frm.doc.items && frm.doc.docstatus==1) {
+ if (frm.doc.for_quantity != frm.doc.transferred_qty) {
+ frm.add_custom_button(__("Material Request"), () => {
+ frm.trigger("make_material_request");
+ });
+ }
+
+ if (frm.doc.for_quantity != frm.doc.transferred_qty) {
+ frm.add_custom_button(__("Material Transfer"), () => {
+ frm.trigger("make_stock_entry");
+ });
+ }
+ }
+
+ if (frm.doc.docstatus == 0) {
+ if (!frm.doc.actual_start_date || !frm.doc.actual_end_date) {
+ frm.trigger("make_dashboard");
+ }
+
+ if (!frm.doc.actual_start_date) {
+ frm.add_custom_button(__("Start Job"), () => {
+ frm.set_value('actual_start_date', frappe.datetime.now_datetime());
+ frm.save();
+ });
+ } else if (!frm.doc.actual_end_date) {
+ frm.add_custom_button(__("Complete Job"), () => {
+ frm.set_value('actual_end_date', frappe.datetime.now_datetime());
+ frm.save();
+ });
+ }
+ }
+ },
+
+ make_dashboard: function(frm) {
+ if(frm.doc.__islocal)
+ return;
+
+ frm.dashboard.refresh();
+ const timer = `
+ <div class="stopwatch" style="font-weight:bold">
+ <span class="hours">00</span>
+ <span class="colon">:</span>
+ <span class="minutes">00</span>
+ <span class="colon">:</span>
+ <span class="seconds">00</span>
+ </div>`;
+
+ var section = frm.dashboard.add_section(timer);
+
+ if (frm.doc.actual_start_date) {
+ let currentIncrement = moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.actual_start_date),"seconds");
+ initialiseTimer();
+
+ function initialiseTimer() {
+ const interval = setInterval(function() {
+ var current = setCurrentIncrement();
+ updateStopwatch(current);
+ }, 1000);
+ }
+
+ function updateStopwatch(increment) {
+ var hours = Math.floor(increment / 3600);
+ var minutes = Math.floor((increment - (hours * 3600)) / 60);
+ var seconds = increment - (hours * 3600) - (minutes * 60);
+
+ $(section).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString());
+ $(section).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString());
+ $(section).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString());
+ }
+
+ function setCurrentIncrement() {
+ currentIncrement += 1;
+ return currentIncrement;
+ }
+ }
+ },
+
+ for_quantity: function(frm) {
+ frm.doc.items = [];
+ frm.call({
+ method: "get_required_items",
+ doc: frm.doc,
+ callback: function() {
+ refresh_field("items");
+ }
+ })
+ },
+
+ make_material_request: function(frm) {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.manufacturing.doctype.job_card.job_card.make_material_request",
+ frm: frm,
+ run_link_triggers: true
+ });
+ },
+
+ make_stock_entry: function(frm) {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.manufacturing.doctype.job_card.job_card.make_stock_entry",
+ frm: frm,
+ run_link_triggers: true
+ });
+ },
+
+ timer: function(frm) {
+ return `<button> Start </button>`
+ }
+});
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
new file mode 100644
index 0000000..443cad8
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -0,0 +1,912 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "PO-JOB.#####",
+ "beta": 0,
+ "creation": "2018-07-09 17:23:29.518745",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "work_order",
+ "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": "Work Order",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Work Order",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 1,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "workstation",
+ "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": "Workstation",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Workstation",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "operation",
+ "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": "Operation",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Operation",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "wip_warehouse",
+ "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": "WIP Warehouse",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Warehouse",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "Today",
+ "fieldname": "posting_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": "Posting Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "company",
+ "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": "Company",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Company",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "for_quantity",
+ "fieldtype": "Float",
+ "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": "For Quantity",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "fieldname": "transferred_qty",
+ "fieldtype": "Float",
+ "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": "Transferred Qty",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "timing_detail",
+ "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": "Timing Detail",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "employee",
+ "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": "Employee",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Employee",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "time_in_mins",
+ "fieldtype": "Float",
+ "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": "Time In Mins",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_13",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "actual_start_date",
+ "fieldtype": "Datetime",
+ "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": "Actual Start Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "actual_end_date",
+ "fieldtype": "Datetime",
+ "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": "Actual End Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_8",
+ "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": "Raw Materials",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "items",
+ "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": "Items",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Job Card Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "columns": 0,
+ "fieldname": "more_information",
+ "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": "More Information",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "operation_id",
+ "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": "Operation ID",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "bom_no",
+ "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": "BOM No",
+ "length": 0,
+ "no_copy": 0,
+ "options": "BOM",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "project",
+ "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": "Project",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Project",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_20",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "remarks",
+ "fieldtype": "Small 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": "Remarks",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "Open",
+ "fieldname": "status",
+ "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": "Status",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Open\nWork In Progress\nCancelled\nCompleted",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "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": "Job Card",
+ "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
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 1,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2018-08-28 16:50:43.576151",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "operation",
+ "track_changes": 1,
+ "track_seen": 0,
+ "track_views": 0
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
new file mode 100644
index 0000000..bce5b90
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -0,0 +1,211 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, 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, time_diff_in_hours, get_datetime
+from frappe.model.mapper import get_mapped_doc
+from frappe.model.document import Document
+
+class JobCard(Document):
+ def validate(self):
+ self.status = 'Open'
+ self.validate_actual_dates()
+ self.set_time_in_mins()
+
+ def validate_actual_dates(self):
+ if get_datetime(self.actual_start_date) > get_datetime(self.actual_end_date):
+ frappe.throw(_("Actual start date must be less than actual end date"))
+
+ if not (self.employee and self.actual_start_date and self.actual_end_date):
+ return
+
+ data = frappe.db.sql(""" select name from `tabJob Card`
+ where
+ ((%(actual_start_date)s > actual_start_date and %(actual_start_date)s < actual_end_date) or
+ (%(actual_end_date)s > actual_start_date and %(actual_end_date)s < actual_end_date) or
+ (%(actual_start_date)s <= actual_start_date and %(actual_end_date)s >= actual_end_date)) and
+ name != %(name)s and employee = %(employee)s and docstatus =1
+ """, {
+ 'actual_start_date': self.actual_start_date,
+ 'actual_end_date': self.actual_end_date,
+ 'employee': self.employee,
+ 'name': self.name
+ }, as_dict=1)
+
+ if data:
+ frappe.throw(_("Start date and end date is overlapping with the job card <a href='#Form/Job Card/{0}'>{1}</a>")
+ .format(data[0].name, data[0].name))
+
+ def set_time_in_mins(self):
+ if self.actual_start_date and self.actual_end_date:
+ self.time_in_mins = time_diff_in_hours(self.actual_end_date, self.actual_start_date) * 60
+
+ def get_required_items(self):
+ if not self.get('work_order'):
+ return
+
+ doc = frappe.get_doc('Work Order', self.get('work_order'))
+ if not doc.transfer_material_against_job_card and doc.skip_transfer:
+ return
+
+ for d in doc.required_items:
+ if not d.operation:
+ frappe.throw(_("Row {0} : Operation is required against the raw material item {1}")
+ .format(d.idx, d.item_code))
+
+ if self.get('operation') == d.operation:
+ child = self.append('items', {
+ 'item_code': d.item_code,
+ 'source_warehouse': d.source_warehouse,
+ 'uom': frappe.db.get_value("Item", d.item_code, 'stock_uom'),
+ 'item_name': d.item_name,
+ 'description': d.description,
+ 'required_qty': (d.required_qty * flt(self.for_quantity)) / doc.qty
+ })
+
+ def on_submit(self):
+ self.validate_dates()
+ self.update_work_order()
+ self.set_transferred_qty()
+
+ def validate_dates(self):
+ if not self.actual_start_date and not self.actual_end_date:
+ frappe.throw(_("Actual start date and actual end date is mandatory"))
+
+ def on_cancel(self):
+ self.update_work_order()
+ self.set_transferred_qty()
+
+ def update_work_order(self):
+ if not self.work_order:
+ return
+
+ data = frappe.db.get_value("Job Card", {'docstatus': 1, 'operation_id': self.operation_id},
+ ['sum(time_in_mins)', 'min(actual_start_date)', 'max(actual_end_date)', 'sum(for_quantity)'])
+
+ if data:
+ time_in_mins, actual_start_date, actual_end_date, for_quantity = data
+
+ wo = frappe.get_doc('Work Order', self.work_order)
+
+ for data in wo.operations:
+ if data.name == self.operation_id:
+ data.completed_qty = for_quantity
+ data.actual_operation_time = time_in_mins
+ data.actual_start_time = actual_start_date
+ data.actual_end_time = actual_end_date
+
+ wo.flags.ignore_validate_update_after_submit = True
+ wo.update_operation_status()
+ wo.calculate_operating_cost()
+ wo.set_actual_dates()
+ wo.save()
+
+ def set_transferred_qty(self):
+ if not self.items:
+ self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
+
+ if self.items:
+ self.transferred_qty = frappe.db.get_value('Stock Entry', {'job_card': self.name,
+ 'work_order': self.work_order, 'docstatus': 1}, 'sum(fg_completed_qty)')
+
+ self.db_set("transferred_qty", self.transferred_qty)
+
+ qty = 0
+ if self.work_order:
+ doc = frappe.get_doc('Work Order', self.work_order)
+ if doc.transfer_material_against_job_card and not doc.skip_transfer:
+ completed = True
+ for d in doc.operations:
+ if d.status != 'Completed':
+ completed = False
+ break
+
+ if completed:
+ job_cards = frappe.get_all('Job Card', filters = {'work_order': self.work_order,
+ 'docstatus': ('!=', 2)}, fields = 'sum(transferred_qty) as qty', group_by='operation_id')
+ qty = min([d.qty for d in job_cards])
+
+ doc.db_set('material_transferred_for_manufacturing', qty)
+
+ self.set_status()
+
+ def set_status(self):
+ status = 'Cancelled' if self.docstatus == 2 else 'Work In Progress'
+
+ if self.for_quantity == self.transferred_qty:
+ status = 'Completed'
+
+ self.db_set('status', status)
+
+def update_job_card_reference(name, fieldname, value):
+ frappe.db.set_value('Job Card', name, fieldname, value)
+
+@frappe.whitelist()
+def make_material_request(source_name, target_doc=None):
+ def update_item(obj, target, source_parent):
+ target.warehouse = source_parent.wip_warehouse
+
+ def set_missing_values(source, target):
+ target.material_request_type = "Material Transfer"
+
+ doclist = get_mapped_doc("Job Card", source_name, {
+ "Job Card": {
+ "doctype": "Material Request",
+ "validation": {
+ "docstatus": ["=", 1]
+ },
+ "field_map": {
+ "name": "job_card",
+ },
+ },
+ "Job Card Item": {
+ "doctype": "Material Request Item",
+ "field_map": {
+ "required_qty": "qty",
+ "uom": "stock_uom"
+ },
+ "postprocess": update_item,
+ }
+ }, target_doc, set_missing_values)
+
+ return doclist
+
+@frappe.whitelist()
+def make_stock_entry(source_name, target_doc=None):
+ def update_item(obj, target, source_parent):
+ target.t_warehouse = source_parent.wip_warehouse
+
+ def set_missing_values(source, target):
+ target.purpose = "Material Transfer for Manufacture"
+ target.from_bom = 1
+ target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0)
+ target.calculate_rate_and_amount()
+ target.set_missing_values()
+
+ doclist = get_mapped_doc("Job Card", source_name, {
+ "Job Card": {
+ "doctype": "Stock Entry",
+ "validation": {
+ "docstatus": ["=", 1]
+ },
+ "field_map": {
+ "name": "job_card",
+ "for_quantity": "fg_completed_qty"
+ },
+ },
+ "Job Card Item": {
+ "doctype": "Stock Entry Detail",
+ "field_map": {
+ "source_warehouse": "s_warehouse",
+ "required_qty": "qty",
+ "uom": "stock_uom"
+ },
+ "postprocess": update_item,
+ }
+ }, target_doc, set_missing_values)
+
+ return doclist
diff --git a/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py b/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py
new file mode 100644
index 0000000..a9811fc
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py
@@ -0,0 +1,12 @@
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'job_card',
+ 'transactions': [
+ {
+ 'label': _('Transactions'),
+ 'items': ['Material Request', 'Stock Entry']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card/job_card_list.js b/erpnext/manufacturing/doctype/job_card/job_card_list.js
new file mode 100644
index 0000000..d40a9fa
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card/job_card_list.js
@@ -0,0 +1,13 @@
+frappe.listview_settings['Job Card'] = {
+ get_indicator: function(doc) {
+ if (doc.status === "Work In Progress") {
+ return [__("Work In Progress"), "orange", "status,=,Work In Progress"];
+ } else if (doc.status === "Completed") {
+ return [__("Completed"), "green", "status,=,Completed"];
+ } else if (doc.docstatus == 2) {
+ return [__("Cancelled"), "red", "status,=,Cancelled"];
+ } else {
+ return [__("Open"), "red", "status,=,Open"];
+ }
+ }
+};
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.js b/erpnext/manufacturing/doctype/job_card/test_job_card.js
new file mode 100644
index 0000000..5dc7805
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Job Card", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Job Card
+ () => frappe.tests.make('Job Card', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
new file mode 100644
index 0000000..ca05fea
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import unittest
+
+class TestJobCard(unittest.TestCase):
+ pass
diff --git a/erpnext/manufacturing/doctype/job_card_item/__init__.py b/erpnext/manufacturing/doctype/job_card_item/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_item/__init__.py
diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json
new file mode 100644
index 0000000..bc9fe10
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json
@@ -0,0 +1,363 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2018-07-09 17:20:44.737289",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "item_code",
+ "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": "Item Code",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "source_warehouse",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 1,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Source Warehouse",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Warehouse",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "uom",
+ "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": "UOM",
+ "length": 0,
+ "no_copy": 0,
+ "options": "UOM",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "item_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": "Item Name",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "description",
+ "fieldtype": "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": "Description",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "qty_section",
+ "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": "Qty",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "required_qty",
+ "fieldtype": "Float",
+ "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": "Required Qty",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "allow_alternative_item",
+ "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": "Allow Alternative Item",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2018-08-28 15:23:48.099459",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card Item",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0,
+ "track_views": 0
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.py b/erpnext/manufacturing/doctype/job_card_item/job_card_item.py
new file mode 100644
index 0000000..373cba2
--- /dev/null
+++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+from frappe.model.document import Document
+
+class JobCardItem(Document):
+ pass
diff --git a/erpnext/manufacturing/doctype/routing/__init__.py b/erpnext/manufacturing/doctype/routing/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/routing/__init__.py
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
new file mode 100644
index 0000000..6cfd0ba
--- /dev/null
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -0,0 +1,58 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Routing', {
+ calculate_operating_cost: function(frm, child) {
+ const operating_cost = flt(flt(child.hour_rate) * flt(child.time_in_mins) / 60, 2);
+ frappe.model.set_value(child.doctype, child.name, "operating_cost", operating_cost);
+ }
+});
+
+frappe.ui.form.on('BOM Operation', {
+ operation: function(frm, cdt, cdn) {
+ const d = locals[cdt][cdn];
+
+ if(!d.operation) return;
+
+ frappe.call({
+ "method": "frappe.client.get",
+ args: {
+ doctype: "Operation",
+ name: d.operation
+ },
+ callback: function (data) {
+ if (data.message.description) {
+ frappe.model.set_value(d.doctype, d.name, "description", data.message.description);
+ }
+
+ if (data.message.workstation) {
+ frappe.model.set_value(d.doctype, d.name, "workstation", data.message.workstation);
+ }
+
+ frm.events.calculate_operating_cost(frm, d);
+ }
+ });
+ },
+
+ workstation: function(frm, cdt, cdn) {
+ const d = locals[cdt][cdn];
+
+ frappe.call({
+ "method": "frappe.client.get",
+ args: {
+ doctype: "Workstation",
+ name: d.workstation
+ },
+ callback: function (data) {
+ frappe.model.set_value(d.doctype, d.name, "base_hour_rate", data.message.hour_rate);
+ frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate);
+ frm.events.calculate_operating_cost(frm, d);
+ }
+ });
+ },
+
+ time_in_mins: function(frm, cdt, cdn) {
+ const d = locals[cdt][cdn];
+ frm.events.calculate_operating_cost(frm, d);
+ }
+});
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/routing/routing.json b/erpnext/manufacturing/doctype/routing/routing.json
new file mode 100644
index 0000000..e864c0c
--- /dev/null
+++ b/erpnext/manufacturing/doctype/routing/routing.json
@@ -0,0 +1,180 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "field:routing_name",
+ "beta": 0,
+ "creation": "2018-07-15 11:03:24.191613",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "routing_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": "Routing 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": 1
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "disabled",
+ "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": "Disabled",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "operations",
+ "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": "BOM Operation",
+ "length": 0,
+ "no_copy": 0,
+ "options": "BOM Operation",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2018-07-15 11:42:41.424793",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Routing",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ },
+ {
+ "amend": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0,
+ "track_views": 0
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/routing/routing.py b/erpnext/manufacturing/doctype/routing/routing.py
new file mode 100644
index 0000000..ecd0ba8
--- /dev/null
+++ b/erpnext/manufacturing/doctype/routing/routing.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+from frappe.model.document import Document
+
+class Routing(Document):
+ pass
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.js b/erpnext/manufacturing/doctype/routing/test_routing.js
new file mode 100644
index 0000000..6cb6549
--- /dev/null
+++ b/erpnext/manufacturing/doctype/routing/test_routing.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Routing", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Routing
+ () => frappe.tests.make('Routing', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
new file mode 100644
index 0000000..53ad152
--- /dev/null
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import unittest
+
+class TestRouting(unittest.TestCase):
+ pass
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index b32db3b..fb8fd26 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -73,53 +73,6 @@
self.assertRaises(StockOverProductionError, s.submit)
- def test_make_time_sheet(self):
- from erpnext.manufacturing.doctype.work_order.work_order import make_timesheet
- wo_order = make_wo_order_test_record(item="_Test FG Item 2",
- planned_start_date=now(), qty=1, do_not_save=True)
-
- wo_order.set_work_order_operations()
- wo_order.insert()
- wo_order.submit()
-
- d = wo_order.operations[0]
- d.completed_qty = flt(d.completed_qty)
-
- name = frappe.db.get_value('Timesheet', {'work_order': wo_order.name}, 'name')
- time_sheet_doc = frappe.get_doc('Timesheet', name)
- self.assertEqual(wo_order.company, time_sheet_doc.company)
- time_sheet_doc.submit()
-
- self.assertEqual(wo_order.name, time_sheet_doc.work_order)
- self.assertEqual((wo_order.qty - d.completed_qty),
- sum([d.completed_qty for d in time_sheet_doc.time_logs]))
-
- manufacturing_settings = frappe.get_doc({
- "doctype": "Manufacturing Settings",
- "allow_production_on_holidays": 0
- })
-
- manufacturing_settings.save()
-
- wo_order.load_from_db()
- self.assertEqual(wo_order.operations[0].status, "Completed")
- self.assertEqual(wo_order.operations[0].completed_qty, wo_order.qty)
-
- self.assertEqual(wo_order.operations[0].actual_operation_time, 60)
- self.assertEqual(wo_order.operations[0].actual_operating_cost, 6000)
-
- time_sheet_doc1 = make_timesheet(wo_order.name, wo_order.company)
- self.assertEqual(len(time_sheet_doc1.get('time_logs')), 0)
-
- time_sheet_doc.cancel()
-
- wo_order.load_from_db()
- self.assertEqual(wo_order.operations[0].status, "Pending")
- self.assertEqual(flt(wo_order.operations[0].completed_qty), 0)
-
- self.assertEqual(flt(wo_order.operations[0].actual_operation_time), 0)
- self.assertEqual(flt(wo_order.operations[0].actual_operating_cost), 0)
-
def test_planned_operating_cost(self):
wo_order = make_wo_order_test_record(item="_Test FG Item 2",
planned_start_date=now(), qty=1, do_not_save=True)
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 013183e..e85b0a5 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -4,7 +4,6 @@
frappe.ui.form.on("Work Order", {
setup: function(frm) {
frm.custom_make_buttons = {
- 'Timesheet': 'Make Timesheet',
'Stock Entry': 'Make Stock Entry',
}
@@ -113,13 +112,11 @@
frm.trigger('show_progress');
}
- if(frm.doc.docstatus == 1 && frm.doc.status != 'Stopped'){
- frm.add_custom_button(__('Make Timesheet'), function(){
- frappe.model.open_mapped_doc({
- method: "erpnext.manufacturing.doctype.work_order.work_order.make_new_timesheet",
- frm: cur_frm
- })
- })
+ if (frm.doc.docstatus === 1 && frm.doc.operations
+ && frm.doc.qty != frm.doc.material_transferred_for_manufacturing) {
+ frm.add_custom_button(__('Make Job Card'), () => {
+ frm.trigger("make_job_card")
+ }).addClass('btn-primary');
}
if(frm.doc.required_items && frm.doc.allow_alternative_item) {
@@ -139,6 +136,113 @@
});
}
}
+
+ if (frm.doc.status == "Completed" &&
+ frm.doc.__onload.backflush_raw_materials_based_on == "Material Transferred for Manufacture") {
+ frm.add_custom_button(__("Make BOM"), () => {
+ frm.trigger("make_bom");
+ });
+ }
+ },
+
+ make_job_card: function(frm) {
+ let qty = 0;
+ const fields = [{
+ fieldtype: "Link",
+ fieldname: "operation",
+ options: "Operation",
+ label: __("Operation"),
+ get_query: () => {
+ const filter_workstation = frm.doc.operations.filter(d => {
+ if (d.status != "Completed") {
+ return d;
+ }
+ });
+
+ return {
+ filters: {
+ name: ["in", (filter_workstation || []).map(d => d.operation)]
+ }
+ };
+ },
+ reqd: true
+ }, {
+ fieldtype: "Link",
+ fieldname: "workstation",
+ options: "Workstation",
+ label: __("Workstation"),
+ get_query: () => {
+ const operation = dialog.get_value("operation");
+ const filter_workstation = frm.doc.operations.filter(d => {
+ if (d.operation == operation) {
+ return d;
+ }
+ });
+
+ return {
+ filters: {
+ name: ["in", (filter_workstation || []).map(d => d.workstation)]
+ }
+ };
+ },
+ onchange: () => {
+ const operation = dialog.get_value("operation");
+ const workstation = dialog.get_value("workstation");
+ if (operation && workstation) {
+ const row = frm.doc.operations.filter(d => d.operation == operation && d.workstation == workstation)[0];
+ qty = frm.doc.qty - row.completed_qty;
+
+ if (qty > 0) {
+ dialog.set_value("qty", qty);
+ }
+ }
+ },
+ reqd: true
+ }, {
+ fieldtype: "Float",
+ fieldname: "qty",
+ label: __("For Quantity"),
+ reqd: true
+ }];
+
+ const dialog = frappe.prompt(fields, function(data) {
+ if (data.qty > qty) {
+ frappe.throw(__("For Quantity must be less than quantity {0}", [qty]));
+ }
+
+ if (data.qty <= 0) {
+ frappe.throw(__("For Quantity must be greater than zero"));
+ }
+
+ frappe.call({
+ method: "erpnext.manufacturing.doctype.work_order.work_order.make_job_card",
+ args: {
+ work_order: frm.doc.name,
+ operation: data.operation,
+ workstation: data.workstation,
+ qty: data.qty
+ },
+ callback: function(r){
+ if (r.message) {
+ var doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
+ }
+ }
+ });
+ }, __("For Job Card"));
+ },
+
+ make_bom: function(frm) {
+ frappe.call({
+ method: "make_bom",
+ doc: frm.doc,
+ callback: function(r){
+ if (r.message) {
+ var doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
+ }
+ }
+ });
},
show_progress: function(frm) {
@@ -189,7 +293,8 @@
frm.set_value('sales_order', "");
frm.trigger('set_sales_order');
erpnext.in_production_item_onchange = true;
- $.each(["description", "stock_uom", "project", "bom_no", "allow_alternative_item"], function(i, field) {
+ $.each(["description", "stock_uom", "project", "bom_no",
+ "allow_alternative_item", "transfer_material_against_job_card"], function(i, field) {
frm.set_value(field, r.message[field]);
});
@@ -235,6 +340,9 @@
before_submit: function(frm) {
frm.toggle_reqd(["fg_warehouse", "wip_warehouse"], true);
frm.fields_dict.required_items.grid.toggle_reqd("source_warehouse", true);
+ if (frm.doc.operations) {
+ frm.fields_dict.operations.grid.toggle_reqd("workstation", true);
+ }
},
set_sales_order: function(frm) {
@@ -316,7 +424,10 @@
}, __("Status"));
}
- if(!frm.doc.skip_transfer){
+ const show_start_btn = (frm.doc.skip_transfer
+ || frm.doc.transfer_material_against_job_card) ? 0 : 1;
+
+ if (show_start_btn){
if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty))
&& frm.doc.status != 'Stopped') {
frm.has_start_btn = true;
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index aef2ac4..df9dd83 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -559,6 +559,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "operations",
+ "fieldname": "transfer_material_against_job_card",
+ "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": "Transfer Material Against Job Card",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "warehouses",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1639,7 +1672,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-29 06:28:22.983369",
+ "modified": "2018-09-05 06:28:22.983369",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 6995829..1d465d5 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -190,6 +190,9 @@
for purpose, fieldname in (("Manufacture", "produced_qty"),
("Material Transfer for Manufacture", "material_transferred_for_manufacturing")):
+ if (purpose == 'Material Transfer for Manufacture' and
+ self.operations and self.transfer_material_against_job_card):
+ continue
qty = flt(frappe.db.sql("""select sum(fg_completed_qty)
from `tabStock Entry` where work_order=%s and docstatus=1
@@ -209,9 +212,6 @@
production_plan = frappe.get_doc('Production Plan', self.production_plan)
production_plan.run_method("update_produced_qty", self.produced_qty, self.production_plan_item)
- def before_submit(self):
- self.make_time_logs()
-
def on_submit(self):
if not self.wip_warehouse:
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
@@ -223,18 +223,27 @@
self.update_completed_qty_in_material_request()
self.update_planned_qty()
self.update_ordered_qty()
+ self.create_job_card()
def on_cancel(self):
self.validate_cancel()
frappe.db.set(self,'status', 'Cancelled')
self.update_work_order_qty_in_so()
- self.delete_timesheet()
+ self.delete_job_card()
self.update_completed_qty_in_material_request()
self.update_planned_qty()
self.update_ordered_qty()
self.update_reserved_qty_for_production()
+ def create_job_card(self):
+ for row in self.operations:
+ if not row.workstation:
+ frappe.throw(_("Row {0}: select the workstation against the operation {1}")
+ .format(row.idx, row.operation))
+
+ create_job_card(self, row, auto_create=True)
+
def validate_cancel(self):
if self.status == "Stopped":
frappe.throw(_("Stopped Work Order cannot be cancelled, Unstop it first to cancel"))
@@ -312,6 +321,17 @@
""" % ", ".join(["%s"]*len(bom_list)), tuple(bom_list), as_dict=1)
self.set('operations', operations)
+
+ if self.use_multi_level_bom and self.get('operations') and self.get('items'):
+ raw_material_operations = [d.operation for d in self.get('items')]
+ operations = [d.operation for d in self.get('operations')]
+
+ for operation in raw_material_operations:
+ if operation not in operations:
+ self.append('operations', {
+ 'operation': operation
+ })
+
self.calculate_time()
def calculate_time(self):
@@ -335,99 +355,6 @@
return holidays[holiday_list]
- def make_time_logs(self, open_new=False):
- """Capacity Planning. Plan time logs based on earliest availablity of workstation after
- Planned Start Date. Time logs will be created and remain in Draft mode and must be submitted
- before manufacturing entry can be made."""
-
- if not self.operations:
- return
-
- timesheets = []
- plan_days = frappe.db.get_single_value("Manufacturing Settings", "capacity_planning_for_days") or 30
-
- timesheet = make_timesheet(self.name, self.company)
- timesheet.set('time_logs', [])
-
- for i, d in enumerate(self.operations):
-
- if d.status != 'Completed':
- self.set_start_end_time_for_workstation(d, i)
-
- args = self.get_operations_data(d)
-
- add_timesheet_detail(timesheet, args)
- original_start_time = d.planned_start_time
-
- # validate operating hours if workstation [not mandatory] is specified
- try:
- timesheet.validate_time_logs()
- except OverlapError:
- if frappe.message_log: frappe.message_log.pop()
- timesheet.schedule_for_work_order(d.idx)
- except WorkstationHolidayError:
- if frappe.message_log: frappe.message_log.pop()
- timesheet.schedule_for_work_order(d.idx)
-
- from_time, to_time = self.get_start_end_time(timesheet, d.name)
-
- if date_diff(from_time, original_start_time) > plan_days:
- frappe.throw(_("Unable to find Time Slot in the next {0} days for Operation {1}").format(plan_days, d.operation))
- break
-
- d.planned_start_time = from_time
- d.planned_end_time = to_time
- d.db_update()
-
- if timesheet and open_new:
- return timesheet
-
- if timesheet and timesheet.get("time_logs"):
- timesheet.save()
- timesheets.append(getlink("Timesheet", timesheet.name))
-
- self.planned_end_date = self.operations[-1].planned_end_time
- if timesheets:
- frappe.local.message_log = []
- frappe.msgprint(_("Timesheet created:") + "\n" + "\n".join(timesheets))
-
- def get_operations_data(self, data):
- return {
- 'from_time': get_datetime(data.planned_start_time),
- 'hours': data.time_in_mins / 60.0,
- 'to_time': get_datetime(data.planned_end_time),
- 'project': self.project,
- 'operation': data.operation,
- 'operation_id': data.name,
- 'workstation': data.workstation,
- 'completed_qty': flt(self.qty) - flt(data.completed_qty)
- }
-
- def set_start_end_time_for_workstation(self, data, index):
- """Set start and end time for given operation. If first operation, set start as
- `planned_start_date`, else add time diff to end time of earlier operation."""
-
- if index == 0:
- data.planned_start_time = self.planned_start_date
- else:
- data.planned_start_time = get_datetime(self.operations[index-1].planned_end_time)\
- + get_mins_between_operations()
-
- data.planned_end_time = get_datetime(data.planned_start_time) + relativedelta(minutes = data.time_in_mins)
-
- if data.planned_start_time == data.planned_end_time:
- frappe.throw(_("Capacity Planning Error"))
-
- def get_start_end_time(self, timesheet, operation_id):
- for data in timesheet.time_logs:
- if data.operation_id == operation_id:
- return data.from_time, data.to_time
-
- def check_operation_fits_in_working_hours(self, d):
- """Raises expection if operation is longer than working hours in the given workstation."""
- from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours
- check_if_within_operating_hours(d.workstation, d.operation, d.planned_start_time, d.planned_end_time)
-
def update_operation_status(self):
for d in self.get("operations"):
if not d.completed_qty:
@@ -451,9 +378,9 @@
if actual_end_dates:
self.actual_end_date = max(actual_end_dates)
- def delete_timesheet(self):
- for timesheet in frappe.get_all("Timesheet", ["name"], {"work_order": self.name}):
- frappe.delete_doc("Timesheet", timesheet.name)
+ def delete_job_card(self):
+ for d in frappe.get_all("Job Card", ["name"], {"work_order": self.name}):
+ frappe.delete_doc("Job Card", d.name)
def validate_production_item(self):
if frappe.db.get_value("Item", self.production_item, "has_variants"):
@@ -523,6 +450,7 @@
else:
for item in sorted(item_dict.values(), key=lambda d: d['idx']):
self.append('required_items', {
+ 'operation': item.operation,
'item_code': item.item_code,
'item_name': item.item_name,
'description': item.description,
@@ -573,6 +501,30 @@
d.db_set('consumed_qty', flt(consumed_qty), update_modified = False)
+ def make_bom(self):
+ data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse
+ from `tabStock Entry Detail` sed, `tabStock Entry` se
+ where se.name = sed.parent and se.purpose = 'Manufacture'
+ and (sed.t_warehouse is null or sed.t_warehouse = '') and se.docstatus = 1
+ and se.work_order = %s""", (self.name), as_dict=1)
+
+ bom = frappe.new_doc("BOM")
+ bom.item = self.production_item
+ bom.conversion_rate = 1
+
+ for d in data:
+ bom.append('items', {
+ 'item_code': d.item_code,
+ 'qty': d.qty,
+ 'source_warehouse': d.s_warehouse
+ })
+
+ if self.operations:
+ bom.set('operations', self.operations)
+ bom.with_operations = 1
+
+ bom.set_bom_material_details()
+ return bom
@frappe.whitelist()
def get_item_details(item, project = None):
@@ -609,8 +561,12 @@
else:
frappe.throw(_("Default BOM for {0} not found").format(item))
- res['project'] = project or frappe.db.get_value('BOM', res['bom_no'], 'project')
- res['allow_alternative_item'] = frappe.db.get_value('BOM', res['bom_no'], 'allow_alternative_item')
+ bom_data = frappe.db.get_value('BOM', res['bom_no'],
+ ['project', 'allow_alternative_item', 'transfer_material_against_job_card'], as_dict=1)
+
+ res['project'] = project or bom_data.project
+ res['allow_alternative_item'] = bom_data.allow_alternative_item
+ res['transfer_material_against_job_card'] = bom_data.transfer_material_against_job_card
res.update(check_if_scrap_warehouse_mandatory(res["bom_no"]))
return res
@@ -668,25 +624,6 @@
return stock_entry.as_dict()
@frappe.whitelist()
-def make_timesheet(work_order, company):
- timesheet = frappe.new_doc("Timesheet")
- timesheet.employee = ""
- timesheet.work_order = work_order
- timesheet.company = company
- return timesheet
-
-@frappe.whitelist()
-def add_timesheet_detail(timesheet, args):
- if isinstance(timesheet, string_types):
- timesheet = frappe.get_doc('Timesheet', timesheet)
-
- if isinstance(args, string_types):
- args = json.loads(args)
-
- timesheet.append('time_logs', args)
- return timesheet
-
-@frappe.whitelist()
def get_default_warehouse():
wip_warehouse = frappe.db.get_single_value("Manufacturing Settings",
"default_wip_warehouse")
@@ -695,16 +632,6 @@
return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse}
@frappe.whitelist()
-def make_new_timesheet(source_name, target_doc=None):
- po = frappe.get_doc('Work Order', source_name)
- ts = po.make_time_logs(open_new=True)
-
- if not ts or not ts.get('time_logs'):
- frappe.throw(_("Already completed"))
-
- return ts
-
-@frappe.whitelist()
def stop_unstop(work_order, status):
""" Called from client side on Stop/Unstop event"""
@@ -730,3 +657,40 @@
""", (production_item, production_item))
return out
+
+@frappe.whitelist()
+def make_job_card(work_order, operation, workstation, qty=0):
+ work_order = frappe.get_doc('Work Order', work_order)
+ row = get_work_order_operation_data(work_order, operation, workstation)
+ if row:
+ return create_job_card(work_order, row, qty)
+
+def create_job_card(work_order, row, qty=0, auto_create=False):
+ doc = frappe.new_doc("Job Card")
+ doc.update({
+ 'work_order': work_order.name,
+ 'operation': row.operation,
+ 'workstation': row.workstation,
+ 'posting_date': nowdate(),
+ 'for_quantity': qty or work_order.get('qty', 0),
+ 'operation_id': row.name,
+ 'bom_no': work_order.bom_no,
+ 'project': work_order.project,
+ 'company': work_order.company,
+ 'wip_warehouse': work_order.wip_warehouse
+ })
+
+ if work_order.transfer_material_against_job_card and not work_order.skip_transfer:
+ doc.get_required_items()
+
+ if auto_create:
+ doc.flags.ignore_mandatory = True
+ doc.insert()
+ frappe.msgprint(_("Job card {0} created").format(doc.name))
+
+ return doc
+
+def get_work_order_operation_data(work_order, operation, workstation):
+ for d in work_order.operations:
+ if d.operation == operation and d.workstation == workstation:
+ return d
diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
index 9b7c9a3..02fbfcd 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
@@ -5,7 +5,7 @@
'fieldname': 'work_order',
'transactions': [
{
- 'items': ['Stock Entry', 'Timesheet']
+ 'items': ['Stock Entry', 'Job Card']
}
]
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
index badeb91..6dbb494 100644
--- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
+++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
@@ -19,6 +19,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "operation",
+ "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": "Operation",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Operation",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "item_code",
"fieldtype": "Link",
"hidden": 0,
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 512e33b..3b3c25b 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -434,7 +434,7 @@
erpnext.patches.v8_6.update_timesheet_company_from_PO
erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager
erpnext.patches.v8_5.remove_project_type_property_setter
-erpnext.patches.v8_7.add_more_gst_fields #21-09-2017
+erpnext.patches.v8_7.sync_india_custom_fields
erpnext.patches.v8_7.fix_purchase_receipt_status
erpnext.patches.v8_6.rename_bom_update_tool
erpnext.patches.v8_7.set_offline_in_pos_settings #11-09-17
@@ -486,10 +486,8 @@
erpnext.patches.v10_0.fichier_des_ecritures_comptables_for_france
erpnext.patches.v10_0.update_assessment_plan
erpnext.patches.v10_0.update_assessment_result
-erpnext.patches.v10_0.added_extra_gst_custom_field
erpnext.patches.v10_0.set_default_payment_terms_based_on_company
erpnext.patches.v10_0.update_sales_order_link_to_purchase_order
-erpnext.patches.v10_0.added_extra_gst_custom_field_in_gstr2 #2018-02-13
erpnext.patches.v10_0.item_barcode_childtable_migrate
erpnext.patches.v10_0.rename_price_to_rate_in_pricing_rule
erpnext.patches.v10_0.set_currency_in_pricing_rule
@@ -563,3 +561,5 @@
erpnext.patches.v11_0.update_hub_url # 2018-08-31 # 2018-09-03
erpnext.patches.v10_0.set_discount_amount
erpnext.patches.v10_0.recalculate_gross_margin_for_project
+erpnext.patches.v11_0.make_job_card
+erpnext.patches.v11_0.redesign_healthcare_billing_work_flow
diff --git a/erpnext/patches/v10_0/added_extra_gst_custom_field.py b/erpnext/patches/v10_0/added_extra_gst_custom_field.py
deleted file mode 100644
index 000e8fd..0000000
--- a/erpnext/patches/v10_0/added_extra_gst_custom_field.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import frappe
-from erpnext.regional.india.setup import make_custom_fields
-
-def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
- if not company:
- return
-
- frappe.reload_doc("hr", "doctype", "Employee Tax Exemption Declaration")
- frappe.reload_doc("hr", "doctype", "Employee Tax Exemption Proof Submission")
-
- make_custom_fields(update=False)
\ No newline at end of file
diff --git a/erpnext/patches/v10_0/remove_and_copy_fields_in_physician.py b/erpnext/patches/v10_0/remove_and_copy_fields_in_physician.py
index f632c94..0739671 100644
--- a/erpnext/patches/v10_0/remove_and_copy_fields_in_physician.py
+++ b/erpnext/patches/v10_0/remove_and_copy_fields_in_physician.py
@@ -5,7 +5,7 @@
frappe.reload_doc("healthcare", "doctype", "physician")
frappe.reload_doc("healthcare", "doctype", "physician_service_unit_schedule")
- if frappe.db.has_column('Physician', 'physician_schedule'):
+ if frappe.db.has_column('Physician', 'physician_schedules'):
for doc in frappe.get_all('Physician'):
_doc = frappe.get_doc('Physician', doc.name)
if _doc.physician_schedule:
diff --git a/erpnext/patches/v11_0/make_job_card.py b/erpnext/patches/v11_0/make_job_card.py
new file mode 100644
index 0000000..ad9a9af
--- /dev/null
+++ b/erpnext/patches/v11_0/make_job_card.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2017, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.manufacturing.doctype.work_order.work_order import create_job_card
+
+def execute():
+ frappe.reload_doc('manufacturing', 'doctype', 'work_order')
+ frappe.reload_doc('manufacturing', 'doctype', 'work_order_item')
+ frappe.reload_doc('manufacturing', 'doctype', 'job_card')
+ frappe.reload_doc('manufacturing', 'doctype', 'job_card_item')
+
+ fieldname = frappe.db.get_value('DocField', {'fieldname': 'work_order', 'parent': 'Timesheet'}, 'fieldname')
+ if not fieldname:
+ fieldname = frappe.db.get_value('DocField', {'fieldname': 'production_order', 'parent': 'Timesheet'}, 'fieldname')
+ if not fieldname: return
+
+ for d in frappe.db.sql("""select %(fieldname)s, name from tabTimesheet
+ where (%(fieldname)s is not null and %(fieldname)s != '') and docstatus = 0""",
+ {'fieldname': fieldname}, as_dict=1):
+ if d[fieldname]:
+ doc = frappe.get_doc('Work Order', d[fieldname])
+ for row in doc.operations:
+ create_job_card(doc, row, auto_create=True)
+ frappe.delete_doc('Timesheet', d.name)
diff --git a/erpnext/patches/v11_0/redesign_healthcare_billing_work_flow.py b/erpnext/patches/v11_0/redesign_healthcare_billing_work_flow.py
new file mode 100644
index 0000000..dc7ff13
--- /dev/null
+++ b/erpnext/patches/v11_0/redesign_healthcare_billing_work_flow.py
@@ -0,0 +1,65 @@
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from erpnext.domains.healthcare import data
+from frappe.modules import scrub, get_doctype_module
+
+sales_invoice_referenced_doc = {
+ "Patient Appointment": "sales_invoice",
+ "Patient Encounter": "invoice",
+ "Lab Test": "invoice",
+ "Lab Prescription": "invoice",
+ "Sample Collection": "invoice"
+}
+
+def execute():
+ frappe.reload_doc('accounts', 'doctype', 'loyalty_program')
+ frappe.reload_doc('accounts', 'doctype', 'sales_invoice_item')
+
+ if "healthcare" not in frappe.get_active_domains():
+ return
+
+ healthcare_custom_field_in_sales_invoice()
+ for si_ref_doc in sales_invoice_referenced_doc:
+ if frappe.db.exists('DocType', si_ref_doc):
+ frappe.reload_doc(get_doctype_module(si_ref_doc), 'doctype', scrub(si_ref_doc))
+
+ if frappe.db.has_column(si_ref_doc, sales_invoice_referenced_doc[si_ref_doc]) \
+ and frappe.db.has_column(si_ref_doc, 'invoiced'):
+ # Set Reference DocType and Reference Docname
+ doc_list = frappe.db.sql("""
+ select name from `tab{0}`
+ where {1} is not null
+ """.format(si_ref_doc, sales_invoice_referenced_doc[si_ref_doc]))
+ if doc_list:
+ frappe.reload_doc(get_doctype_module("Sales Invoice"), 'doctype', 'sales_invoice')
+ for doc_id in doc_list:
+ invoice_id = frappe.db.get_value(si_ref_doc, doc_id[0], sales_invoice_referenced_doc[si_ref_doc])
+ invoice = frappe.get_doc("Sales Invoice", invoice_id)
+ if invoice.items:
+ marked = False
+ if not marked:
+ for item_line in invoice.items:
+ marked = True
+ frappe.db.sql("""
+ update `tabSales Invoice Item`
+ set reference_dt = '{0}', reference_dn = '{1}'
+ where name = '{2}'
+ """.format(si_ref_doc, doc_id[0], item_line.name))
+
+ # Documents mark invoiced for submitted sales invoice
+ frappe.db.sql("""
+ update `tab{0}` doc, `tabSales Invoice` si
+ set doc.invoiced = 1
+ where si.docstatus = 1 and doc.{1} = si.name
+ """.format(si_ref_doc, sales_invoice_referenced_doc[si_ref_doc]))
+
+def healthcare_custom_field_in_sales_invoice():
+ frappe.reload_doc('healthcare', 'doctype', 'patient')
+ frappe.reload_doc('healthcare', 'doctype', 'healthcare_practitioner')
+ if data['custom_fields']:
+ create_custom_fields(data['custom_fields'])
+
+ frappe.db.sql("""
+ delete from `tabCustom Field`
+ where fieldname = 'appointment' and options = 'Patient Appointment'
+ """)
diff --git a/erpnext/patches/v8_7/add_more_gst_fields.py b/erpnext/patches/v8_7/add_more_gst_fields.py
deleted file mode 100644
index d2085e0..0000000
--- a/erpnext/patches/v8_7/add_more_gst_fields.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import frappe
-from erpnext.regional.india.setup import make_custom_fields
-
-def execute():
- company = frappe.get_all('Company', filters = {'country': 'India'})
- if not company:
- return
-
- frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_declaration')
- frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_proof_submission')
- make_custom_fields()
\ No newline at end of file
diff --git a/erpnext/patches/v10_0/added_extra_gst_custom_field_in_gstr2.py b/erpnext/patches/v8_7/sync_india_custom_fields.py
similarity index 61%
rename from erpnext/patches/v10_0/added_extra_gst_custom_field_in_gstr2.py
rename to erpnext/patches/v8_7/sync_india_custom_fields.py
index 12aa5fd..323b5bc 100644
--- a/erpnext/patches/v10_0/added_extra_gst_custom_field_in_gstr2.py
+++ b/erpnext/patches/v8_7/sync_india_custom_fields.py
@@ -6,11 +6,14 @@
if not company:
return
+ frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_declaration')
+ frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_proof_submission')
+
for doctype in ["Sales Invoice", "Delivery Note", "Purchase Invoice"]:
frappe.db.sql("""delete from `tabCustom Field` where dt = %s
and fieldname in ('port_code', 'shipping_bill_number', 'shipping_bill_date')""", doctype)
- make_custom_fields(update=False)
+ make_custom_fields()
frappe.db.sql("""
update `tabCustom Field`
@@ -18,4 +21,8 @@
where fieldname = 'reason_for_issuing_document'
""")
-
+ frappe.db.sql("""
+ update tabAddress
+ set gst_state_number=concat("0", gst_state_number)
+ where ifnull(gst_state_number, '') != '' and gst_state_number<10
+ """)
\ No newline at end of file
diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json
index d1ec38c..e5198de 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.json
+++ b/erpnext/projects/doctype/timesheet/timesheet.json
@@ -457,6 +457,39 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 1,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "User",
+ "length": 0,
+ "no_copy": 0,
+ "options": "User",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "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,
@@ -514,73 +547,6 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "collapsible_depends_on": "",
- "columns": 0,
- "depends_on": "work_order",
- "fieldname": "work_detail",
- "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": "Work Detail",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "work_order",
- "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": "Work Order",
- "length": 0,
- "no_copy": 1,
- "options": "Work Order",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
@@ -1066,7 +1032,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-21 14:44:32.912004",
+ "modified": "2018-08-28 14:44:32.912004",
"modified_by": "Administrator",
"module": "Projects",
"name": "Timesheet",
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 7dc121c..f48c0c6 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -97,19 +97,13 @@
self.set_status()
def on_cancel(self):
- self.update_work_order(None)
self.update_task_and_project()
def on_submit(self):
self.validate_mandatory_fields()
- self.update_work_order(self.name)
self.update_task_and_project()
def validate_mandatory_fields(self):
- if self.work_order:
- work_order = frappe.get_doc("Work Order", self.work_order)
- pending_qty = flt(work_order.qty) - flt(work_order.produced_qty)
-
for data in self.time_logs:
if not data.from_time and not data.to_time:
frappe.throw(_("Row {0}: From Time and To Time is mandatory.").format(data.idx))
@@ -120,41 +114,6 @@
if flt(data.hours) == 0.0:
frappe.throw(_("Row {0}: Hours value must be greater than zero.").format(data.idx))
- if self.work_order and flt(data.completed_qty) == 0:
- frappe.throw(_("Row {0}: Completed Qty must be greater than zero.").format(data.idx))
-
- if self.work_order and flt(pending_qty) < flt(data.completed_qty) and flt(pending_qty) > 0:
- frappe.throw(_("Row {0}: Completed Qty cannot be more than {1} for operation {2}").format(data.idx, pending_qty, data.operation),
- OverWorkLoggedError)
-
- def update_work_order(self, time_sheet):
- if self.work_order:
- pro = frappe.get_doc('Work Order', self.work_order)
-
- for timesheet in self.time_logs:
- for data in pro.operations:
- if data.name == timesheet.operation_id:
- summary = self.get_actual_timesheet_summary(timesheet.operation_id)
- data.time_sheet = time_sheet
- data.completed_qty = summary.completed_qty
- data.actual_operation_time = summary.mins
- data.actual_start_time = summary.from_time
- data.actual_end_time = summary.to_time
-
- pro.flags.ignore_validate_update_after_submit = True
- pro.update_operation_status()
- pro.calculate_operating_cost()
- pro.set_actual_dates()
- pro.save()
-
- def get_actual_timesheet_summary(self, operation_id):
- """Returns 'Actual Operating Time'. """
- return frappe.db.sql("""select
- sum(tsd.hours*60) as mins, sum(tsd.completed_qty) as completed_qty, min(tsd.from_time) as from_time,
- max(tsd.to_time) as to_time from `tabTimesheet Detail` as tsd, `tabTimesheet` as ts where
- ts.work_order = %s and tsd.operation_id = %s and ts.docstatus=1 and ts.name = tsd.parent""",
- (self.work_order, operation_id), as_dict=1)[0]
-
def update_task_and_project(self):
tasks, projects = [], []
@@ -176,16 +135,12 @@
def validate_time_logs(self):
for data in self.get('time_logs'):
- self.check_workstation_timings(data)
self.validate_overlap(data)
def validate_overlap(self, data):
settings = frappe.get_single('Projects Settings')
- if self.work_order:
- self.validate_overlap_for("workstation", data, data.workstation, settings.ignore_workstation_time_overlap)
- else:
- self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
- self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap)
+ self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
+ self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap)
def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
if not value or ignore_validation:
@@ -227,48 +182,6 @@
return existing[0] if existing else None
- def check_workstation_timings(self, args):
- """Checks if **Time Log** is between operating hours of the **Workstation**."""
- if args.workstation and args.from_time and args.to_time:
- check_if_within_operating_hours(args.workstation, args.operation, args.from_time, args.to_time)
-
- def schedule_for_work_order(self, index):
- for data in self.time_logs:
- if data.idx == index:
- self.move_to_next_day(data) #check for workstation holiday
- self.move_to_next_non_overlapping_slot(data) #check for overlap
- break
-
- def move_to_next_non_overlapping_slot(self, data):
- overlapping = self.get_overlap_for("workstation", data, data.workstation)
- if overlapping:
- time_sheet = self.get_last_working_slot(overlapping.name, data.workstation)
- data.from_time = get_datetime(time_sheet.to_time) + get_mins_between_operations()
- data.to_time = self.get_to_time(data)
- self.check_workstation_working_day(data)
-
- def get_last_working_slot(self, time_sheet, workstation):
- return frappe.db.sql(""" select max(from_time) as from_time, max(to_time) as to_time
- from `tabTimesheet Detail` where workstation = %(workstation)s""",
- {'workstation': workstation}, as_dict=True)[0]
-
- def move_to_next_day(self, data):
- """Move start and end time one day forward"""
- self.check_workstation_working_day(data)
-
- def check_workstation_working_day(self, data):
- while True:
- try:
- self.check_workstation_timings(data)
- break
- except WorkstationHolidayError:
- if frappe.message_log: frappe.message_log.pop()
- data.from_time = get_datetime(data.from_time) + timedelta(hours=24)
- data.to_time = self.get_to_time(data)
-
- def get_to_time(self, data):
- return get_datetime(data.from_time) + timedelta(hours=data.hours)
-
def update_cost(self):
for data in self.time_logs:
if data.activity_type or data.billable:
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 07ddcb4..b3ba053 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -343,7 +343,8 @@
weight_per_unit: item.weight_per_unit,
weight_uom: item.weight_uom,
uom : item.uom,
- pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : ''
+ pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
+ cost_center: item.cost_center
}
},
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 8648687..36746cd 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -79,6 +79,37 @@
"options": "Finance Book"
},
{
+ "fieldname":"cost_center",
+ "label": __("Cost Center"),
+ "fieldtype": "MultiSelect",
+ get_data: function() {
+ var cost_centers = frappe.query_report.get_filter_value("cost_center") || "";
+
+ const values = cost_centers.split(/\s*,\s*/).filter(d => d);
+ const txt = cost_centers.match(/[^,\s*]*$/)[0] || '';
+ let data = [];
+
+ frappe.call({
+ type: "GET",
+ method:'frappe.desk.search.search_link',
+ async: false,
+ no_spinner: true,
+ args: {
+ doctype: "Cost Center",
+ txt: txt,
+ filters: {
+ "company": frappe.query_report.get_filter_value("company"),
+ "name": ["not in", values]
+ }
+ },
+ callback: function(r) {
+ data = r.results;
+ }
+ });
+ return data;
+ }
+ },
+ {
"fieldname":"from_fiscal_year",
"label": __("Start Year"),
"fieldtype": "Link",
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 682577b..434b58c 100644
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -201,7 +201,18 @@
} else {
return options[0];
}
- }
+ },
+ copy_parent_value_in_all_row: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) {
+ var d = locals[dt][dn];
+ if(d[fieldname]){
+ var cl = doc[table_fieldname] || [];
+ for(var i = 0; i < cl.length; i++) {
+ cl[i][fieldname] = doc[parent_fieldname];
+ }
+ }
+ refresh_field(table_fieldname);
+ },
+
});
erpnext.utils.select_alternate_items = function(opts) {
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index da9e469..07a42ff 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -171,7 +171,7 @@
dict(fieldname='gst_state', label='GST State', fieldtype='Select',
options='\n'.join(states), insert_after='gstin'),
dict(fieldname='gst_state_number', label='GST State Number',
- fieldtype='Int', insert_after='gst_state', read_only=1),
+ fieldtype='Data', insert_after='gst_state', read_only=1),
],
'Purchase Invoice': invoice_gst_fields + purchase_invoice_gst_fields,
'Sales Invoice': invoice_gst_fields + sales_invoice_gst_fields,
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index edd5519..fa2d2af 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -4,8 +4,9 @@
from __future__ import unicode_literals
import frappe, json
from frappe import _
-from frappe.utils import flt
+from frappe.utils import flt, formatdate
from datetime import date
+from six import iteritems
def execute(filters=None):
return Gstr1Report(filters).run()
@@ -73,12 +74,17 @@
row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total))
elif fieldname == "invoice_value":
row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total)
+ elif fieldname in ('posting_date', 'shipping_bill_date'):
+ row.append(formatdate(invoice_details.get(fieldname), 'dd-MMM-YY'))
+ elif fieldname == "export_type":
+ export_type = "WPAY" if invoice_details.get(fieldname)=="With Payment of Tax" else "WOPAY"
+ row.append(export_type)
else:
row.append(invoice_details.get(fieldname))
taxable_value = sum([abs(net_amount)
for item_code, net_amount in self.invoice_items.get(invoice).items() if item_code in items])
- row += [tax_rate, taxable_value]
+ row += [tax_rate or 0, taxable_value]
return row, taxable_value
@@ -195,6 +201,12 @@
if unidentified_gst_accounts:
frappe.msgprint(_("Following accounts might be selected in GST Settings:")
+ "<br>" + "<br>".join(unidentified_gst_accounts), alert=True)
+
+ # Build itemised tax for export invoices where tax table is blank
+ for invoice, items in iteritems(self.invoice_items):
+ if invoice not in self.items_based_on_tax_rate \
+ and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
+ self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
def get_gst_accounts(self):
self.gst_accounts = frappe._dict()
@@ -250,7 +262,7 @@
{
"fieldname": "posting_date",
"label": "Invoice date",
- "fieldtype": "Date",
+ "fieldtype": "Data",
"width":80
},
{
@@ -261,7 +273,7 @@
},
{
"fieldname": "place_of_supply",
- "label": "Place of Supply",
+ "label": "Place Of Supply",
"fieldtype": "Data",
"width":100
},
@@ -303,7 +315,7 @@
{
"fieldname": "posting_date",
"label": "Invoice date",
- "fieldtype": "Date",
+ "fieldtype": "Data",
"width": 100
},
{
@@ -314,7 +326,7 @@
},
{
"fieldname": "place_of_supply",
- "label": "Place of Supply",
+ "label": "Place Of Supply",
"fieldtype": "Data",
"width": 120
},
@@ -357,7 +369,7 @@
{
"fieldname": "posting_date",
"label": "Invoice/Advance Receipt date",
- "fieldtype": "Date",
+ "fieldtype": "Data",
"width": 120
},
{
@@ -368,12 +380,6 @@
"width":120
},
{
- "fieldname": "posting_date",
- "label": "Invoice/Advance Receipt date",
- "fieldtype": "Date",
- "width": 120
- },
- {
"fieldname": "reason_for_issuing_document",
"label": "Reason For Issuing document",
"fieldtype": "Data",
@@ -381,7 +387,7 @@
},
{
"fieldname": "place_of_supply",
- "label": "Place of Supply",
+ "label": "Place Of Supply",
"fieldtype": "Data",
"width": 120
},
@@ -416,7 +422,7 @@
self.invoice_columns = [
{
"fieldname": "place_of_supply",
- "label": "Place of Supply",
+ "label": "Place Of Supply",
"fieldtype": "Data",
"width": 120
},
@@ -459,7 +465,7 @@
{
"fieldname": "posting_date",
"label": "Invoice date",
- "fieldtype": "Date",
+ "fieldtype": "Data",
"width": 120
},
{
@@ -483,7 +489,7 @@
{
"fieldname": "shipping_bill_date",
"label": "Shipping Bill Date",
- "fieldtype": "Date",
+ "fieldtype": "Data",
"width": 120
}
]
diff --git a/erpnext/selling/doctype/sales_order/sales_order_list.js b/erpnext/selling/doctype/sales_order/sales_order_list.js
index 43c91b9..69c2100 100644
--- a/erpnext/selling/doctype/sales_order/sales_order_list.js
+++ b/erpnext/selling/doctype/sales_order/sales_order_list.js
@@ -1,20 +1,25 @@
frappe.listview_settings['Sales Order'] = {
add_fields: ["base_grand_total", "customer_name", "currency", "delivery_date",
"per_delivered", "per_billed", "status", "order_type", "name"],
- get_indicator: function(doc) {
- if(doc.status==="Closed"){
+ get_indicator: function (doc) {
+ if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"];
} else if (doc.order_type !== "Maintenance"
&& flt(doc.per_delivered, 6) < 100 && frappe.datetime.get_diff(doc.delivery_date) < 0) {
- // to bill & overdue
+ // not delivered & overdue
return [__("Overdue"), "red", "per_delivered,<,100|delivery_date,<,Today|status,!=,Closed"];
} else if (doc.order_type !== "Maintenance"
- && flt(doc.per_delivered, 6) < 100 && doc.status!=="Closed") {
+ && flt(doc.per_delivered, 6) < 100 && doc.status !== "Closed") {
// not delivered
- if(flt(doc.per_billed, 6) < 100) {
+ if (flt(doc.grand_total) === 0) {
+ // not delivered (zero-amount order)
+
+ return [__("To Deliver"), "orange",
+ "per_delivered,<,100|grand_total,=,0|status,!=,Closed"];
+ } else if (flt(doc.per_billed, 6) < 100) {
// not delivered & not billed
return [__("To Deliver and Bill"), "orange",
@@ -27,13 +32,13 @@
}
} else if ((doc.order_type === "Maintenance" || flt(doc.per_delivered, 6) == 100)
- && flt(doc.per_billed, 6) < 100 && doc.status!=="Closed") {
-
+ && flt(doc.grand_total) !== 0 && flt(doc.per_billed, 6) < 100 && doc.status !== "Closed") {
// to bill
+
return [__("To Bill"), "orange", "per_delivered,=,100|per_billed,<,100|status,!=,Closed"];
- } else if((doc.order_type === "Maintenance" || flt(doc.per_delivered, 6) == 100)
- && flt(doc.per_billed, 6) == 100 && doc.status!=="Closed") {
+ } else if ((doc.order_type === "Maintenance" || flt(doc.per_delivered, 6) == 100)
+ && (flt(doc.grand_total) === 0 || flt(doc.per_billed, 6) == 100) && doc.status !== "Closed") {
return [__("Completed"), "green", "per_delivered,=,100|per_billed,=,100|status,!=,Closed"];
}
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index f8fd1b3..538ea55 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -15,7 +15,7 @@
class TestSalesOrder(unittest.TestCase):
def tearDown(self):
- pass
+ frappe.set_user("Administrator")
def test_make_material_request(self):
so = make_sales_order(do_not_submit=True)
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 96d1116..9a245e2 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -837,7 +837,7 @@
"label": "Create Chart Of Accounts Based On",
"length": 0,
"no_copy": 0,
- "options": "\nStandard Template\nExisting Company\n\n\n",
+ "options": "\nStandard Template\nExisting Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -871,7 +871,7 @@
"label": "Chart Of Accounts Template",
"length": 0,
"no_copy": 1,
- "options": "\n\n\n",
+ "options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -2836,7 +2836,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2018-09-01 16:03:30.716918",
+ "modified": "2018-09-07 16:03:30.716918",
"modified_by": "cave@aperture.com",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 0d9dbe6..a2f87c5 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -167,7 +167,7 @@
}
erpnext.stock.delivery_note.set_print_hide(doc, dt, dn);
- if(doc.docstatus==1 && !doc.auto_repeat) {
+ if(doc.docstatus==1 && !doc.is_return && !doc.auto_repeat) {
cur_frm.add_custom_button(__('Subscription'), function() {
erpnext.utils.make_subscription(doc.doctype, doc.name)
}, __("Make"))
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index e39c8ab..0ce6232 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -464,6 +464,39 @@
"collapsible": 0,
"columns": 0,
"depends_on": "is_return",
+ "fieldname": "issue_credit_note",
+ "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": "Issue Credit 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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "is_return",
"fieldname": "return_against",
"fieldtype": "Link",
"hidden": 0,
@@ -2790,12 +2823,12 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
- "allow_on_submit": 0,
+ "allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "transporter",
- "fieldtype": "Data",
+ "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -2806,11 +2839,12 @@
"label": "Transporter ID",
"length": 0,
"no_copy": 0,
+ "options": "Driver",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -2822,7 +2856,7 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
- "allow_on_submit": 0,
+ "allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
@@ -2844,7 +2878,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": "150px",
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -2857,7 +2891,7 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
- "allow_on_submit": 0,
+ "allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
@@ -2878,7 +2912,7 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -2890,7 +2924,7 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
- "allow_on_submit": 0,
+ "allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
@@ -2910,7 +2944,7 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -2954,12 +2988,12 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
- "allow_on_submit": 0,
+ "allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "vehicle_no",
- "fieldtype": "Data",
+ "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -2970,11 +3004,12 @@
"label": "Vehicle No",
"length": 0,
"no_copy": 0,
+ "options": "Vehicle",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -2986,7 +3021,7 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
- "allow_on_submit": 0,
+ "allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
@@ -3007,7 +3042,7 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -3019,13 +3054,13 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
- "allow_on_submit": 0,
+ "allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "",
"fieldname": "lr_no",
- "fieldtype": "Data",
+ "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -3038,11 +3073,12 @@
"no_copy": 0,
"oldfieldname": "lr_no",
"oldfieldtype": "Data",
+ "options": "Delivery Trip",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": "100px",
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -3055,7 +3091,7 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
- "allow_on_submit": 0,
+ "allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
@@ -3079,7 +3115,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": "100px",
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -4165,7 +4201,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2018-08-21 14:44:46.764951",
+ "modified": "2018-08-30 03:50:25.791869",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 1df07a2..6e45273 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -2,19 +2,18 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
+
import frappe
-
-from frappe.utils import flt, cint
-
-from frappe import msgprint, _
import frappe.defaults
-from frappe.model.utils import get_fetch_values
-from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.selling_controller import SellingController
-from frappe.desk.notifications import clear_doctype_notifications
from erpnext.stock.doctype.batch.batch import set_batch_nos
-from frappe.contacts.doctype.address.address import get_company_address
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no
+from frappe import _
+from frappe.contacts.doctype.address.address import get_company_address
+from frappe.desk.notifications import clear_doctype_notifications
+from frappe.model.mapper import get_mapped_doc
+from frappe.model.utils import get_fetch_values
+from frappe.utils import cint, flt
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@@ -170,12 +169,12 @@
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
if e in check_list:
- msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
+ frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
check_list.append(e)
else:
if f in chk_dupl_itm:
- msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
+ frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
chk_dupl_itm.append(f)
@@ -213,7 +212,8 @@
if not self.is_return:
self.check_credit_limit()
-
+ elif self.issue_credit_note:
+ self.make_return_invoice()
# Updating stock ledger should always be called after updating prevdoc status,
# because updating reserved qty in bin depends upon updated delivered qty in SO
self.update_stock_ledger()
@@ -311,11 +311,20 @@
for dn in set(updated_delivery_notes):
dn_doc = self if (dn == self.name) else frappe.get_doc("Delivery Note", dn)
- if dn_doc.net_total > 0:
- dn_doc.update_billing_percentage(update_modified=update_modified)
+ dn_doc.update_billing_percentage(update_modified=update_modified)
self.load_from_db()
+ def make_return_invoice(self):
+ try:
+ return_invoice = make_sales_invoice(self.name)
+ return_invoice.is_return = True
+ return_invoice.save()
+ return_invoice.submit()
+ frappe.msgprint(_("Credit Note {0} has been created automatically").format(return_invoice.name))
+ except:
+ frappe.throw(_("Could not create Credit Note automatically, please uncheck 'Issue Credit Note' and submit again"))
+
def update_billed_amount_based_on_so(so_detail, update_modified=True):
# Billed against Sales Order directly
billed_against_so = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
@@ -400,7 +409,7 @@
# set company address
target.update(get_company_address(target.company))
if target.company_address:
- target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address))
+ target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address))
def update_item(source_doc, target_doc, source_parent):
target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0)
@@ -452,6 +461,11 @@
target_doc.contact = source_parent.contact_person
target_doc.customer_contact = source_parent.contact_display
+ # Append unique Delivery Notes in Delivery Trip
+ delivery_notes.append(target_doc.delivery_note)
+
+ delivery_notes = []
+
doclist = get_mapped_doc("Delivery Note", source_name, {
"Delivery Note": {
"doctype": "Delivery Trip",
@@ -464,7 +478,8 @@
"field_map": {
"parent": "delivery_note"
},
- "postprocess": update_stop_details,
+ "condition": lambda item: item.parent not in delivery_notes,
+ "postprocess": update_stop_details
}
}, target_doc)
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
index f1ad929..9ec2a38 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
@@ -1,14 +1,13 @@
frappe.listview_settings['Delivery Note'] = {
- add_fields: ["customer", "customer_name", "base_grand_total", "per_installed", "per_billed",
- "transporter_name", "grand_total", "is_return", "status"],
- get_indicator: function(doc) {
- if(cint(doc.is_return)==1) {
+ add_fields: ["grand_total", "is_return", "per_billed", "status"],
+ get_indicator: function (doc) {
+ if (cint(doc.is_return) == 1) {
return [__("Return"), "darkgrey", "is_return,=,Yes"];
- } else if(doc.status==="Closed") {
+ } else if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"];
- } else if (flt(doc.per_billed, 2) < 100) {
+ } else if (doc.grand_total !== 0 && flt(doc.per_billed, 2) < 100) {
return [__("To Bill"), "orange", "per_billed,<,100"];
- } else if (flt(doc.per_billed, 2) == 100) {
+ } else if (doc.grand_total === 0 || flt(doc.per_billed, 2) == 100) {
return [__("Completed"), "green", "per_billed,=,100"];
}
}
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 3683695..026d83c 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -569,6 +569,74 @@
dt = make_delivery_trip(dn.name)
self.assertEqual(dn.name, dt.delivery_stops[0].delivery_note)
+ def test_delivery_note_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
+ accounts_settings.save()
+ cost_center = "_Test Cost Center for BS Account - _TC"
+ create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
+
+ company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
+ set_perpetual_inventory(1, company)
+
+ set_valuation_method("_Test Item", "FIFO")
+
+ make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100)
+
+ stock_in_hand_account = get_inventory_account('_Test Company')
+ dn = create_delivery_note(cost_center=cost_center)
+
+ gl_entries = get_gl_entries("Delivery Note", dn.name)
+ self.assertTrue(gl_entries)
+
+ expected_values = {
+ "Cost of Goods Sold - _TC": {
+ "cost_center": cost_center
+ },
+ stock_in_hand_account: {
+ "cost_center": cost_center
+ }
+ }
+ for i, gle in enumerate(gl_entries):
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+ set_perpetual_inventory(0, company)
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+
+ def test_delivery_note_for_disable_allow_cost_center_in_entry_of_bs_account(self):
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+ cost_center = "_Test Cost Center - _TC"
+
+ company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
+ set_perpetual_inventory(1, company)
+
+ set_valuation_method("_Test Item", "FIFO")
+
+ make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100)
+
+ stock_in_hand_account = get_inventory_account('_Test Company')
+ dn = create_delivery_note()
+
+ gl_entries = get_gl_entries("Delivery Note", dn.name)
+
+ self.assertTrue(gl_entries)
+ expected_values = {
+ "Cost of Goods Sold - _TC": {
+ "cost_center": cost_center
+ },
+ stock_in_hand_account: {
+ "cost_center": None
+ }
+ }
+ for i, gle in enumerate(gl_entries):
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+ set_perpetual_inventory(0, company)
+
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
args = frappe._dict(args)
@@ -589,7 +657,7 @@
"rate": args.rate or 100,
"conversion_factor": 1.0,
"expense_account": "Cost of Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no,
"target_warehouse": args.target_warehouse
})
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
index 36f71a7..364bc6b 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
@@ -428,7 +428,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
- "reqd": 0,
+ "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@@ -608,7 +608,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-29 14:44:36.993178",
+ "modified": "2018-08-30 02:31:49.400138",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Trip",
@@ -661,6 +661,5 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "track_seen": 0
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index 9e55f8c..5f291c3 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -3,16 +3,49 @@
# For license information, please see license.txt
from __future__ import unicode_literals
+
import datetime
+
import frappe
from frappe import _
-from frappe.model.document import Document
-from frappe.utils.user import get_user_fullname
-from frappe.utils import getdate, cstr, get_datetime
from frappe.contacts.doctype.address.address import get_address_display
+from frappe.model.document import Document
+from frappe.utils import cstr, get_datetime, getdate, get_link_to_form
+from frappe.utils.user import get_user_fullname
+
class DeliveryTrip(Document):
- pass
+ def on_submit(self):
+ self.update_delivery_notes()
+
+ def on_cancel(self):
+ self.update_delivery_notes(delete=True)
+
+ def update_delivery_notes(self, delete=False):
+ delivery_notes = list(set([stop.delivery_note for stop in self.delivery_stops if stop.delivery_note]))
+
+ update_fields = {
+ "transporter": self.driver,
+ "transporter_name": self.driver_name,
+ "transport_mode": "Road",
+ "vehicle_no": self.vehicle,
+ "vehicle_type": "Regular",
+ "lr_no": self.name,
+ "lr_date": self.date
+ }
+
+ for delivery_note in delivery_notes:
+ note_doc = frappe.get_doc("Delivery Note", delivery_note)
+
+ for field, value in update_fields.items():
+ value = None if delete else value
+ setattr(note_doc, field, value)
+
+ note_doc.save()
+
+ delivery_notes = [get_link_to_form("Delivery Note", note) for note in delivery_notes]
+ frappe.msgprint(_("Delivery Notes {0} updated".format(", ".join(delivery_notes))))
+
def get_default_contact(out, name):
@@ -191,4 +224,4 @@
def format_address(address):
"""Customer Address format """
address = frappe.get_doc('Address', address)
- return '{}, {}, {}, {}'.format(address.address_line1, address.city, address.pincode, address.country)
\ No newline at end of file
+ return '{}, {}, {}, {}'.format(address.address_line1, address.city, address.pincode, address.country)
diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json
index 9927265..c0285cb 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -779,6 +779,39 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "job_card",
+ "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": "Job Card",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Job Card",
+ "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
}
],
"has_web_view": 0,
@@ -793,7 +826,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2018-08-30 07:28:01.070112",
+ "modified": "2018-09-05 07:28:01.070112",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request",
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 730ec3e..42c8370 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -15,6 +15,7 @@
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
from erpnext.buying.utils import check_for_closed_status, validate_for_items
from erpnext.stock.doctype.item.item import get_item_defaults
+from erpnext.manufacturing.doctype.job_card.job_card import update_job_card_reference
from six import string_types
@@ -92,6 +93,9 @@
if self.material_request_type == 'Purchase':
self.validate_budget()
+ if self.job_card:
+ update_job_card_reference(self.job_card, 'material_request', self.name)
+
def before_save(self):
self.set_status(update=True)
@@ -144,6 +148,8 @@
def on_cancel(self):
self.update_requested_qty()
self.update_requested_qty_in_production_plan()
+ if self.job_card:
+ update_job_card_reference(self.job_card, 'material_request', None)
def update_completed_qty(self, mr_items=None, update_modified=True):
if self.material_request_type == "Purchase":
@@ -407,7 +413,11 @@
def set_missing_values(source, target):
target.purpose = source.material_request_type
+ if source.job_card:
+ target.purpose = 'Material Transfer for Manufacture'
+
target.run_method("calculate_rate_and_amount")
+ target.set_job_card_data()
doclist = get_mapped_doc("Material Request", source_name, {
"Material Request": {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 5c370d3..e482f58 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -470,4 +470,4 @@
@frappe.whitelist()
def update_purchase_receipt_status(docname, status):
pr = frappe.get_doc("Purchase Receipt", docname)
- pr.update_status(status)
\ No newline at end of file
+ pr.update_status(status)
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
index 5c57fb5..e1d5b08 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
@@ -1,14 +1,13 @@
frappe.listview_settings['Purchase Receipt'] = {
- add_fields: ["supplier", "supplier_name", "base_grand_total", "is_subcontracted",
- "transporter_name", "is_return", "status", "per_billed"],
- get_indicator: function(doc) {
- if(cint(doc.is_return)==1) {
+ add_fields: ["is_return", "grand_total", "status", "per_billed"],
+ get_indicator: function (doc) {
+ if (cint(doc.is_return) == 1) {
return [__("Return"), "darkgrey", "is_return,=,Yes"];
- } else if(doc.status==="Closed") {
+ } else if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"];
- } else if (flt(doc.per_billed, 2) < 100) {
+ } else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) < 100) {
return [__("To Bill"), "orange", "per_billed,<,100"];
- } else if (flt(doc.per_billed, 2) == 100) {
+ } else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) == 100) {
return [__("Completed"), "green", "per_billed,=,100"];
}
}
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 6e2863e..a2da924 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -333,11 +333,78 @@
pr.cancel()
serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or []
self.assertEquals(len(serial_nos), 0)
- frappe.db.sql("delete from `tabLocation")
+ #frappe.db.sql("delete from `tabLocation")
frappe.db.sql("delete from `tabAsset`")
+ def test_purchase_receipt_for_enable_allow_cost_center_in_entry_of_bs_account(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
+ accounts_settings.save()
+ cost_center = "_Test Cost Center for BS Account - _TC"
+ create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
+
+ if not frappe.db.exists('Location', 'Test Location'):
+ frappe.get_doc({
+ 'doctype': 'Location',
+ 'location_name': 'Test Location'
+ }).insert()
+
+ pr = make_purchase_receipt(cost_center=cost_center)
+ set_perpetual_inventory(1, pr.company)
+ stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
+ gl_entries = get_gl_entries("Purchase Receipt", pr.name)
+
+ self.assertTrue(gl_entries)
+
+ expected_values = {
+ "Stock Received But Not Billed - _TC": {
+ "cost_center": cost_center
+ },
+ stock_in_hand_account: {
+ "cost_center": cost_center
+ }
+ }
+ for i, gle in enumerate(gl_entries):
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+ set_perpetual_inventory(0, pr.company)
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+
+ def test_purchase_receipt_for_disable_allow_cost_center_in_entry_of_bs_account(self):
+ accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
+ accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
+ accounts_settings.save()
+
+ if not frappe.db.exists('Location', 'Test Location'):
+ frappe.get_doc({
+ 'doctype': 'Location',
+ 'location_name': 'Test Location'
+ }).insert()
+
+ pr = make_purchase_receipt()
+ set_perpetual_inventory(1, pr.company)
+ stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
+ gl_entries = get_gl_entries("Purchase Receipt", pr.name)
+
+ self.assertTrue(gl_entries)
+
+ expected_values = {
+ "Stock Received But Not Billed - _TC": {
+ "cost_center": None
+ },
+ stock_in_hand_account: {
+ "cost_center": None
+ }
+ }
+ for i, gle in enumerate(gl_entries):
+ self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
+
+ set_perpetual_inventory(0, pr.company)
+
def get_gl_entries(voucher_type, voucher_no):
- return frappe.db.sql("""select account, debit, credit
+ return frappe.db.sql("""select account, debit, credit, cost_center
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
order by account desc""", (voucher_type, voucher_no), as_dict=1)
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index fa3501a..0356b0e 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -654,7 +654,7 @@
work_order: function() {
var me = this;
this.toggle_enable_bom();
- if(!me.frm.doc.work_order) {
+ if(!me.frm.doc.work_order || me.frm.doc.job_card) {
return;
}
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index 0e3fbec..35f8c27 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -1943,6 +1943,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "job_card",
+ "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": "Job Card",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Job Card",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -2015,7 +2048,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-29 06:27:59.630826",
+ "modified": "2018-09-05 06:27:59.630826",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f723fcf..b7dbda2 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -17,6 +17,7 @@
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit, get_serial_nos
import json
+from erpnext.manufacturing.doctype.job_card.job_card import update_job_card_reference
from six import string_types, itervalues, iteritems
@@ -59,6 +60,7 @@
self.validate_batch()
self.validate_inspection()
self.validate_fg_completed_qty()
+ self.set_job_card_data()
if not self.from_bom:
self.fg_completed_qty = 0.0
@@ -88,6 +90,9 @@
self.update_so_in_serial_number()
+ if self.job_card:
+ update_job_card_reference(self.job_card, 'stock_entry', self.name)
+
def on_cancel(self):
if self.purchase_order and self.purpose == "Subcontract":
@@ -102,6 +107,18 @@
self.make_gl_entries_on_cancel()
self.update_cost_in_project()
+ if self.job_card:
+ update_job_card_reference(self.job_card, 'stock_entry', None)
+
+ def set_job_card_data(self):
+ if self.job_card and not self.work_order:
+ data = frappe.db.get_value('Job Card',
+ self.job_card, ['for_quantity', 'work_order', 'bom_no'], as_dict=1)
+ self.fg_completed_qty = data.for_quantity
+ self.work_order = data.work_order
+ self.from_bom = 1
+ self.bom_no = data.bom_no
+
def validate_work_order_status(self):
pro_doc = frappe.get_doc("Work Order", self.work_order)
if pro_doc.status == 'Completed':
@@ -584,6 +601,10 @@
if pro_doc.status == 'Stopped':
frappe.throw(_("Transaction not allowed against stopped Work Order {0}").format(self.work_order))
+ if self.job_card:
+ job_doc = frappe.get_doc('Job Card', self.job_card)
+ job_doc.set_transferred_qty()
+
if self.work_order:
pro_doc = frappe.get_doc("Work Order", self.work_order)
_validate_work_order(pro_doc)
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index a04d81d..c3cb437 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -392,15 +392,14 @@
price_list_rate = (args.rate / args.get('conversion_factor')
if args.get("conversion_factor") else args.rate)
- name = frappe.db.get_value('Item Price',
- {'item_code': args.item_code, 'price_list': args.price_list, 'currency': args.currency}, 'name')
-
- if name:
- item_price = frappe.get_doc('Item Price', name)
- item_price.price_list_rate = price_list_rate
- item_price.save()
- frappe.msgprint(_("Item Price updated for {0} in Price List {1}").format(args.item_code,
- args.price_list))
+ item_price = frappe.db.get_value('Item Price',
+ {'item_code': args.item_code, 'price_list': args.price_list, 'currency': args.currency},
+ ['name', 'price_list_rate'], as_dict=1)
+ if item_price and item_price.name:
+ if item_price.price_list_rate != price_list_rate:
+ frappe.db.set_value('Item Price', item_price.name, "price_list_rate", price_list_rate)
+ frappe.msgprint(_("Item Price updated for {0} in Price List {1}").format(args.item_code,
+ args.price_list), alert=True)
else:
item_price = frappe.get_doc({
"doctype": "Item Price",
diff --git a/erpnext/stock/report/stock_balance/stock_balance.json b/erpnext/stock/report/stock_balance/stock_balance.json
index 4afbf75..2f20b20 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.json
+++ b/erpnext/stock/report/stock_balance/stock_balance.json
@@ -1,17 +1,17 @@
{
"add_total_row": 1,
- "apply_user_permissions": 1,
"creation": "2014-10-10 17:58:11.577901",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 2,
"is_standard": "Yes",
- "modified": "2017-02-24 20:10:13.764665",
+ "modified": "2018-08-14 15:24:41.395557",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Balance",
"owner": "Administrator",
+ "prepared_report": 1,
"ref_doctype": "Stock Ledger Entry",
"report_name": "Stock Balance",
"report_type": "Script Report",
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
index 937c0a2..51b9b0c 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
@@ -27,10 +27,16 @@
}
},
{
+ "fieldname":"item_group",
+ "label": __("Item Group"),
+ "fieldtype": "Link",
+ "options": "Item Group"
+ },
+ {
"fieldname":"brand",
"label": __("Brand"),
"fieldtype": "Link",
"options": "Brand"
}
]
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index 89a256c..3e6e5a5 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -39,6 +39,9 @@
if filters.brand and filters.brand != item.brand:
continue
+
+ elif filters.item_group and filters.item_group != item.item_group:
+ continue
elif filters.company and filters.company != company:
continue