diff --git a/.gitignore b/.gitignore index 2b52a4956fc..68272c7d4d1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,6 @@ latest_updates.json dist/ erpnext/docs/current *.swp -<<<<<<< HEAD *.swo __pycache__ *~ -======= ->>>>>>> master diff --git a/README.md b/README.md index c5363e8489b..61860e0db86 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Includes: Accounting, Inventory, Manufacturing, CRM, Sales, Purchase, Project Management, HRMS. Requires MariaDB. -ERPNext is built on the [Frappé](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & JavaScript. +ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & JavaScript. - [User Guide](https://erpnext.org/docs/user) - [Discussion Forum](https://discuss.erpnext.com/) @@ -39,7 +39,7 @@ System and user credentials are listed on the download page. GNU/General Public License (see LICENSE.txt) -The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappé Technologies Pvt Ltd (Frappé) and Contributors. +The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors. --- @@ -55,19 +55,19 @@ The ERPNext code is licensed as GNU General Public License (v3) and the Document ## Logo and Trademark -The brand name ERPNext and the logo are trademarks of Frappé Technologies Pvt. Ltd. +The brand name ERPNext and the logo are trademarks of Frappe Technologies Pvt. Ltd. ### Introduction -Frappé Technologies Pvt. Ltd. (Frappé) owns and oversees the trademarks for the ERPNext name and logos. We have developed this trademark usage policy with the following goals in mind: +Frappe Technologies Pvt. Ltd. (Frappe) owns and oversees the trademarks for the ERPNext name and logos. We have developed this trademark usage policy with the following goals in mind: - We’d like to make it easy for anyone to use the ERPNext name or logo for community-oriented efforts that help spread and improve ERPNext. - We’d like to make it clear how ERPNext-related businesses and projects can (and cannot) use the ERPNext name and logo. - We’d like to make it hard for anyone to use the ERPNext name and logo to unfairly profit from, trick or confuse people who are looking for official ERPNext resources. -### Frappé Trademark Usage Policy +### Frappe Trademark Usage Policy -Permission from Frappé is required to use the ERPNext name or logo as part of any project, product, service, domain or company name. +Permission from Frappe is required to use the ERPNext name or logo as part of any project, product, service, domain or company name. We will grant permission to use the ERPNext name and logo for projects that meet the following criteria: @@ -78,7 +78,7 @@ Your project neither promotes nor is associated with entities that currently fai Use of the ERPNext name and logo is additionally allowed in the following situations: -All other ERPNext-related businesses or projects can use the ERPNext name and logo to refer to and explain their services, but they cannot use them as part of a product, project, service, domain, or company name and they cannot use them in any way that suggests an affiliation with or endorsement by ERPNext or Frappé Technologies or the ERPNext open source project. For example, a consulting company can describe its business as “123 Web Services, offering ERPNext consulting for small businesses,” but cannot call its business “The ERPNext Consulting Company.” +All other ERPNext-related businesses or projects can use the ERPNext name and logo to refer to and explain their services, but they cannot use them as part of a product, project, service, domain, or company name and they cannot use them in any way that suggests an affiliation with or endorsement by ERPNext or Frappe Technologies or the ERPNext open source project. For example, a consulting company can describe its business as “123 Web Services, offering ERPNext consulting for small businesses,” but cannot call its business “The ERPNext Consulting Company.” Similarly, it’s OK to use the ERPNext logo as part of a page that describes your products or services, but it is not OK to use it as part of your company or product logo or branding itself. Under no circumstances is it permitted to use ERPNext as part of a top-level domain name. @@ -86,6 +86,6 @@ We do not allow the use of the trademark in advertising, including AdSense/AdWor Please note that it is not the goal of this policy to limit commercial activity around ERPNext. We encourage ERPNext-based businesses, and we would love to see hundreds of them. -When in doubt about your use of the ERPNext name or logo, please contact Frappé Technologies for clarification. +When in doubt about your use of the ERPNext name or logo, please contact Frappe Technologies for clarification. (inspired by WordPress) diff --git a/attributions.md b/attributions.md index 21a44fb253a..9cd6eb8f8f2 100644 --- a/attributions.md +++ b/attributions.md @@ -1,6 +1,6 @@ ## ERPNext includes these public works -For Frappé Framework, please see attributions.md at https://github.com/frappe/frappe/ +For Frappe Framework, please see attributions.md at https://github.com/frappe/frappe/ #### Images diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 2dfd6e1d024..36219896865 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '10.1.43' +__version__ = '10.1.45' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js index 1a23d5f2093..bb059f673cd 100644 --- a/erpnext/accounts/doctype/account/account.js +++ b/erpnext/accounts/doctype/account/account.js @@ -39,14 +39,19 @@ frappe.ui.form.on('Account', { // credit days and type if customer or supplier frm.set_intro(null); frm.trigger('account_type'); - // show / hide convert buttons frm.trigger('add_toolbar_buttons'); } - frm.add_custom_button(__('Update Account Number'), function () { + frm.add_custom_button(__('Update Account Name / Number'), function () { frm.trigger("update_account_number"); }); } + + if(!frm.doc.__islocal) { + frm.add_custom_button(__('Merge Account'), function () { + frm.trigger("merge_account"); + }); + } }, account_type: function (frm) { if (frm.doc.is_group == 0) { @@ -92,32 +97,75 @@ frappe.ui.form.on('Account', { } }, - update_account_number: function(frm) { + merge_account: function(frm) { var d = new frappe.ui.Dialog({ - title: __('Update Account Number'), + title: __('Merge with Existing Account'), fields: [ { - "label": "Account Number", - "fieldname": "account_number", + "label" : "Name", + "fieldname": "name", "fieldtype": "Data", - "reqd": 1 + "reqd": 1, + "default": frm.doc.name } ], primary_action: function() { var data = d.get_values(); - if(data.account_number === frm.doc.account_number) { + frappe.call({ + method: "erpnext.accounts.doctype.account.account.merge_account", + args: { + old: frm.doc.name, + new: data.name, + is_group: frm.doc.is_group, + root_type: frm.doc.root_type, + company: frm.doc.company + }, + callback: function(r) { + if(!r.exc) { + if(r.message) { + frappe.set_route("Form", "Account", r.message); + } + d.hide(); + } + } + }); + }, + primary_action_label: __('Merge') + }); + d.show(); + }, + + update_account_number: function(frm) { + var d = new frappe.ui.Dialog({ + title: __('Update Account Number / Name'), + fields: [ + { + "label": "Account Name", + "fieldname": "account_name", + "fieldtype": "Data", + "reqd": 1, + "default": frm.doc.account_name + }, + { + "label": "Account Number", + "fieldname": "account_number", + "fieldtype": "Data", + "default": frm.doc.account_number + } + ], + primary_action: function() { + var data = d.get_values(); + if(data.account_number === frm.doc.account_number && data.account_name === frm.doc.account_name) { d.hide(); return; } frappe.call({ - method: "erpnext.accounts.utils.update_number_field", + method: "erpnext.accounts.doctype.account.account.update_account_number", args: { - doctype_name: frm.doc.doctype, - name: frm.doc.name, - field_name: d.fields[0].fieldname, - field_value: data.account_number, - company: frm.doc.company, + account_number: data.account_number, + account_name: data.account_name, + name: frm.doc.name }, callback: function(r) { if(!r.exc) { @@ -125,6 +173,7 @@ frappe.ui.form.on('Account', { frappe.set_route("Form", "Account", r.message); } else { frm.set_value("account_number", data.account_number); + frm.set_value("account_name", data.account_name); } d.hide(); } @@ -135,4 +184,4 @@ frappe.ui.form.on('Account', { }); d.show(); } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index de28a59f8ca..5bb55ec2ca5 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -2,7 +2,7 @@ "allow_copy": 1, "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 1, + "allow_rename": 0, "beta": 0, "creation": "2013-01-30 12:49:46", "custom": 0, @@ -625,7 +625,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-07 15:37:25.962506", + "modified": "2018-07-08 09:47:04.287841", "modified_by": "Administrator", "module": "Accounts", "name": "Account", diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index b92e4232493..5ea835898a9 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -167,53 +167,6 @@ class Account(NestedSet): super(Account, self).on_trash(True) - def before_rename(self, old, new, merge=False): - # Add company abbr if not provided - from erpnext.setup.doctype.company.company import get_name_with_abbr - new_account = get_name_with_abbr(new, self.company) - if not merge: - new_account = get_name_with_number(new_account, self.account_number) - else: - # Validate properties before merging - if not frappe.db.exists("Account", new): - throw(_("Account {0} does not exist").format(new)) - - val = list(frappe.db.get_value("Account", new_account, - ["is_group", "root_type", "company"])) - - if val != [self.is_group, self.root_type, self.company]: - throw(_("""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company""")) - - if self.is_group and frappe.db.get_value("Account", new, "parent_account") == old: - frappe.db.set_value("Account", new, "parent_account", - frappe.db.get_value("Account", old, "parent_account")) - - return new_account - - def after_rename(self, old, new, merge=False): - super(Account, self).after_rename(old, new, merge) - - if not merge: - new_acc = frappe.db.get_value("Account", new, ["account_name", "account_number"], as_dict=1) - - # exclude company abbr - new_parts = new.split(" - ")[:-1] - # update account number and remove from parts - if new_parts[0][0].isdigit(): - # if account number is separate by space, split using space - if len(new_parts) == 1: - new_parts = new.split(" ") - if new_acc.account_number != new_parts[0]: - self.account_number = new_parts[0] - self.db_set("account_number", new_parts[0]) - new_parts = new_parts[1:] - - # update account name - account_name = " - ".join(new_parts) - if new_acc.account_name != account_name: - self.account_name = account_name - self.db_set("account_name", account_name) - def get_parent_account(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select name from tabAccount where is_group = 1 and docstatus != 2 and company = %s @@ -234,11 +187,58 @@ def get_account_currency(account): return frappe.local_cache("account_currency", account, generator) -def get_name_with_number(new_account, account_number): - if account_number and not new_account[0].isdigit(): - new_account = account_number + " - " + new_account - return new_account - - def on_doctype_update(): - frappe.db.add_index("Account", ["lft", "rgt"]) \ No newline at end of file + frappe.db.add_index("Account", ["lft", "rgt"]) + +def get_account_autoname(account_number, account_name, company): + # first validate if company exists + company = frappe.db.get_value("Company", company, ["abbr", "name"], as_dict=True) + if not company: + frappe.throw(_('Company {0} does not exist').format(company)) + + parts = [account_name.strip(), company.abbr] + if cstr(account_number).strip(): + parts.insert(0, cstr(account_number).strip()) + return ' - '.join(parts) + +def validate_account_number(name, account_number, company): + if account_number: + account_with_same_number = frappe.db.get_value("Account", + {"account_number": account_number, "company": company, "name": ["!=", name]}) + if account_with_same_number: + frappe.throw(_("Account Number {0} already used in account {1}") + .format(account_number, account_with_same_number)) + +@frappe.whitelist() +def update_account_number(name, account_name, account_number=None): + + account = frappe.db.get_value("Account", name, ["company"], as_dict=True) + validate_account_number(name, account_number, account.company) + if account_number: + frappe.db.set_value("Account", name, "account_number", account_number.strip()) + frappe.db.set_value("Account", name, "account_name", account_name.strip()) + + new_name = get_account_autoname(account_number, account_name, account.company) + if name != new_name: + frappe.rename_doc("Account", name, new_name, ignore_permissions=1) + return new_name + +@frappe.whitelist() +def merge_account(old, new, is_group, root_type, company): + # Validate properties before merging + if not frappe.db.exists("Account", new): + throw(_("Account {0} does not exist").format(new)) + + val = list(frappe.db.get_value("Account", new, + ["is_group", "root_type", "company"])) + + if val != [cint(is_group), root_type, company]: + throw(_("""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company""")) + + if is_group and frappe.db.get_value("Account", new, "parent_account") == old: + frappe.db.set_value("Account", new, "parent_account", + frappe.db.get_value("Account", old, "parent_account")) + + frappe.rename_doc("Account", old, new, merge=1, ignore_permissions=1) + + return new diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index 55383dc81e4..acaa0966a20 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -5,6 +5,8 @@ from __future__ import unicode_literals import unittest import frappe from erpnext.stock import get_warehouse_account, get_company_default_inventory_account +from erpnext.accounts.doctype.account.account import update_account_number +from erpnext.accounts.doctype.account.account import merge_account class TestAccount(unittest.TestCase): def test_rename_account(self): @@ -21,21 +23,79 @@ class TestAccount(unittest.TestCase): self.assertEqual(account_number, "1210") self.assertEqual(account_name, "Debtors") - frappe.rename_doc("Account", "1210 - Debtors - _TC", "1211 - Debtors 1 - _TC") + new_account_number = "1211-11-4 - 6 - " + new_account_name = "Debtors 1 - Test - " - new_acc = frappe.db.get_value("Account", "1211 - Debtors 1 - _TC", + update_account_number("1210 - Debtors - _TC", new_account_name, new_account_number) + + new_acc = frappe.db.get_value("Account", "1211-11-4 - 6 - - Debtors 1 - Test - - _TC", ["account_name", "account_number"], as_dict=1) - self.assertEqual(new_acc.account_name, "Debtors 1") - self.assertEqual(new_acc.account_number, "1211") - frappe.rename_doc("Account", "1211 - Debtors 1 - _TC", "Debtors 2") + self.assertEqual(new_acc.account_name, "Debtors 1 - Test -") + self.assertEqual(new_acc.account_number, "1211-11-4 - 6 -") - new_acc = frappe.db.get_value("Account", "1211 - Debtors 2 - _TC", - ["account_name", "account_number"], as_dict=1) - self.assertEqual(new_acc.account_name, "Debtors 2") - self.assertEqual(new_acc.account_number, "1211") + frappe.delete_doc("Account", "1211-11-4 - 6 - Debtors 1 - Test - - _TC") - frappe.delete_doc("Account", "1211 - Debtors 2 - _TC") + def test_merge_account(self): + if not frappe.db.exists("Account", "Current Assets - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Current Assets" + acc.is_group = 1 + acc.parent_account = "Application of Funds (Assets) - _TC" + acc.company = "_Test Company" + acc.insert() + if not frappe.db.exists("Account", "Securities and Deposits - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Securities and Deposits" + acc.parent_account = "Current Assets - _TC" + acc.is_group = 1 + acc.company = "_Test Company" + acc.insert() + if not frappe.db.exists("Account", "Earnest Money - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Earnest Money" + acc.parent_account = "Securities and Deposits - _TC" + acc.company = "_Test Company" + acc.insert() + if not frappe.db.exists("Account", "Cash In Hand - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Cash In Hand" + acc.is_group = 1 + acc.parent_account = "Current Assets - _TC" + acc.company = "_Test Company" + acc.insert() + if not frappe.db.exists("Account", "Accumulated Depreciation - _TC"): + acc = frappe.new_doc("Account") + acc.account_name = "Accumulated Depreciation" + acc.parent_account = "Fixed Assets - _TC" + acc.company = "_Test Company" + acc.insert() + + doc = frappe.get_doc("Account", "Securities and Deposits - _TC") + parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account") + + self.assertEqual(parent, "Securities and Deposits - _TC") + + merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company) + parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account") + + # Parent account of the child account changes after merging + self.assertEqual(parent, "Cash In Hand - _TC") + + # Old account doesn't exist after merging + self.assertFalse(frappe.db.exists("Account", "Securities and Deposits - _TC")) + + doc = frappe.get_doc("Account", "Current Assets - _TC") + + # Raise error as is_group property doesn't match + self.assertRaises(frappe.ValidationError, merge_account, "Current Assets - _TC",\ + "Accumulated Depreciation - _TC", doc.is_group, doc.root_type, doc.company) + + doc = frappe.get_doc("Account", "Capital Stock - _TC") + + # Raise error as root_type property doesn't match + self.assertRaises(frappe.ValidationError, merge_account, "Capital Stock - _TC",\ + "Softwares - _TC", doc.is_group, doc.root_type, doc.company) def _make_test_records(verbose): from frappe.test_runner import make_test_objects diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 564a93c7c7e..81e8d7a73d0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -40,7 +40,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -72,7 +71,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 1, - "translatable": 0, "unique": 0 }, { @@ -104,7 +102,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -134,7 +131,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -166,7 +162,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -198,7 +193,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -230,7 +224,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -262,7 +255,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -296,7 +288,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -329,7 +320,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -359,7 +349,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -392,7 +381,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -423,7 +411,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -455,7 +442,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -488,7 +474,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -521,7 +506,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -554,7 +538,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -584,7 +567,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -617,7 +599,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -650,7 +631,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -683,7 +663,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -716,7 +695,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -749,7 +727,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -781,7 +758,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -814,7 +790,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -844,7 +819,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -877,7 +851,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -909,7 +882,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -942,7 +914,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -975,7 +946,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1008,7 +978,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1041,7 +1010,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1073,7 +1041,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1105,7 +1072,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1138,7 +1104,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1169,7 +1134,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1199,7 +1163,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1231,7 +1194,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1264,7 +1226,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1296,7 +1257,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1329,7 +1289,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1361,7 +1320,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1392,7 +1350,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1424,7 +1381,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1454,7 +1410,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1486,7 +1441,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1518,7 +1472,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1551,7 +1504,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1584,7 +1536,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1604,7 +1555,7 @@ "in_standard_filter": 0, "label": "Remarks", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", "print_hide": 0, @@ -1615,7 +1566,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1645,7 +1595,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1677,7 +1626,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1709,7 +1657,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1740,7 +1687,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1772,7 +1718,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1803,7 +1748,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1834,7 +1778,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -1848,7 +1791,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-03-10 07:31:49.264576", + "modified": "2018-07-27 01:49:24.720317", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index afc3804ff74..8820161a368 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -31,7 +31,7 @@ frappe.ui.form.on("Payment Request", "refresh", function(frm) { }); }); } - + if(!frm.doc.payment_gateway_account && frm.doc.status == "Initiated") { frm.add_custom_button(__('Make Payment Entry'), function(){ frappe.call({ @@ -49,3 +49,25 @@ frappe.ui.form.on("Payment Request", "refresh", function(frm) { } }); +frappe.ui.form.on("Payment Request", "is_a_subscription", function(frm) { + frm.toggle_reqd("payment_gateway_account", frm.doc.is_a_subscription); + frm.toggle_reqd("subscription_plans", frm.doc.is_a_subscription); + + if (frm.doc.is_a_subscription) { + frappe.call({ + method: "erpnext.accounts.doctype.payment_request.payment_request.get_subscription_details", + args: {"reference_doctype": frm.doc.reference_doctype, "reference_name": frm.doc.reference_name}, + freeze: true, + callback: function(data){ + if(!data.exc) { + $.each(data.message || [], function(i, v){ + var d = frappe.model.add_child(frm.doc, "Subscription Plan Detail", "subscription_plans"); + d.qty = v.qty; + d.plan = v.plan; + }); + frm.refresh_field("subscription_plans"); + } + } + }); + } +}); diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index e21afa47895..4148dce3b4d 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -15,6 +15,7 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -42,11 +43,12 @@ "reqd": 1, "search_index": 0, "set_only_once": 1, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -73,11 +75,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, @@ -106,11 +109,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, @@ -137,11 +141,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, @@ -168,11 +173,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, @@ -198,11 +204,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, @@ -230,11 +237,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, @@ -262,11 +270,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, @@ -295,11 +304,44 @@ "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": "is_a_subscription", + "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": "Is a Subscription", + "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, @@ -328,11 +370,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, @@ -360,11 +403,79 @@ "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, + "collapsible_depends_on": "", + "columns": 0, + "depends_on": "eval:doc.is_a_subscription", + "fieldname": "subscription_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": "Subscription Section", + "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": "subscription_plans", + "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": "Subscription Plans", + "length": 0, + "no_copy": 0, + "options": "Subscription Plan Detail", + "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, @@ -390,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, @@ -421,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, @@ -453,11 +566,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, @@ -484,11 +598,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, @@ -515,11 +630,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, @@ -547,16 +663,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": "payment_gateway_account.payment_gateway", + "fetch_from": "payment_gateway_account.payment_gateway", "fieldname": "payment_gateway", "fieldtype": "Read Only", "hidden": 0, @@ -580,16 +697,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": "payment_gateway_account.payment_account", + "fetch_from": "payment_gateway_account.payment_account", "fieldname": "payment_account", "fieldtype": "Read Only", "hidden": 0, @@ -613,11 +731,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, @@ -644,11 +763,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, @@ -676,11 +796,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, @@ -708,11 +829,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, @@ -739,7 +861,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 } ], @@ -753,7 +875,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-16 22:43:28.136835", + "modified": "2018-06-20 17:06:43.850174", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index a633cc31a61..e7371bdd711 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -12,12 +12,15 @@ from erpnext.accounts.utils import get_account_currency from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry, get_company_defaults from frappe.integrations.utils import get_payment_gateway_controller from frappe.utils.background_jobs import enqueue +from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription +from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate class PaymentRequest(Document): def validate(self): self.validate_reference_document() self.validate_payment_request() self.validate_currency() + self.validate_subscription_details() def validate_reference_document(self): if not self.reference_doctype or not self.reference_name: @@ -33,6 +36,21 @@ class PaymentRequest(Document): if self.payment_account and ref_doc.currency != frappe.db.get_value("Account", self.payment_account, "account_currency"): frappe.throw(_("Transaction currency must be same as Payment Gateway currency")) + def validate_subscription_details(self): + if self.is_a_subscription: + amount = 0 + for subscription_plan in self.subscription_plans: + payment_gateway = frappe.db.get_value("Subscription Plan", subscription_plan.plan, "payment_gateway") + if payment_gateway != self.payment_gateway_account: + frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request'.format(subscription_plan.name))) + + rate = get_plan_rate(subscription_plan.plan, quantity=subscription_plan.qty) + + amount += rate + + if amount != self.grand_total: + frappe.msgprint(_("The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document.".format(self.grand_total, amount))) + def on_submit(self): send_mail = self.payment_gateway_validation() ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) @@ -226,15 +244,19 @@ class PaymentRequest(Document): success_url = shopping_cart_settings.payment_success_url if success_url: redirect_to = ({ - "Orders": "orders", - "Invoices": "invoices", - "My Account": "me" - }).get(success_url, "me") + "Orders": "/orders", + "Invoices": "/invoices", + "My Account": "/me" + }).get(success_url, "/me") else: redirect_to = get_url("/orders/{0}".format(self.reference_name)) return redirect_to + def create_subscription(self, payment_provider, gateway_controller, data): + if payment_provider == "stripe": + return create_stripe_subscription(gateway_controller, data) + @frappe.whitelist(allow_guest=True) def make_payment_request(**args): """Make payment request""" @@ -375,3 +397,14 @@ def get_dummy_message(doc):
{{ _("Thank you for your business!") }}
""", dict(doc=doc, payment_url = '{{ payment_url }}')) + +@frappe.whitelist() +def get_subscription_details(reference_doctype, reference_name): + if reference_doctype == "Sales Invoice": + subscriptions = frappe.db.sql("""SELECT parent as sub_name FROM `tabSubscription Invoice` WHERE invoice=%s""",reference_name, as_dict=1) + subscription_plans = [] + for subscription in subscriptions: + plans = frappe.get_doc("Subscription", subscription.sub_name).plans + for plan in plans: + subscription_plans.append(plan) + return subscription_plans \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.js b/erpnext/accounts/doctype/payment_request/test_payment_request.js new file mode 100644 index 00000000000..070b595fc6a --- /dev/null +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.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: Payment Request", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Payment Request + () => frappe.tests.make('Payment Request', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 4db56f396fa..6eb629cf568 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -4493,7 +4493,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-07-17 02:38:40.310899", + "modified": "2018-07-30 08:35:17.736855", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index f01caad49d8..bd0a4d7fb7e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -5442,7 +5442,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-07-18 13:16:20.918322", + "modified": "2018-07-30 08:34:55.545490", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index 5d64d9a1a08..a58ac3e68a5 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -111,39 +111,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "quantity", - "fieldtype": "Int", - "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": "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -847,7 +814,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-07-11 19:34:44.582203", + "modified": "2018-07-13 15:18:49.016010", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription", diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 72f86f2f60c..fe391619602 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -8,6 +8,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils.data import nowdate, getdate, cint, add_days, date_diff, get_last_day, add_to_date, flt +from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate class Subscription(Document): @@ -244,7 +245,6 @@ class Subscription(Document): # for that reason items_list = self.get_items_from_plans(self.plans, prorate) for item in items_list: - item['qty'] = self.quantity invoice.append('items', item) # Taxes @@ -272,6 +272,10 @@ class Subscription(Document): discount_on = self.apply_additional_discount invoice.apply_additional_discount = discount_on if discount_on else 'Grand Total' + # Subscription period + invoice.from_date = self.current_invoice_start + invoice.to_date = self.current_invoice_end + invoice.flags.ignore_mandatory = True invoice.save() invoice.submit() @@ -283,28 +287,25 @@ class Subscription(Document): """ Returns the `Customer` linked to the `Subscriber` """ - return frappe.get_value('Subscriber', subscriber_name, 'customer') + return frappe.db.get_value('Subscriber', subscriber_name, 'customer') def get_items_from_plans(self, plans, prorate=0): """ Returns the `Item`s linked to `Subscription Plan` """ - plan_items = [plan.plan for plan in plans] - item_details = None + if prorate: + prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start) - if plan_items: - item_details = frappe.db.sql( - 'select item as item_code, cost as rate from `tabSubscription Plan` where name in %s', - (plan_items,), as_dict=1 - ) + items = [] + customer = self.get_customer(self.subscriber) + for plan in plans: + item_code = frappe.db.get_value("Subscription Plan", plan.plan, "item") + if not prorate: + items.append({'item_code': item_code, 'qty': plan.qty, 'rate': get_plan_rate(plan.plan, plan.qty, customer)}) + else: + items.append({'item_code': item_code, 'qty': plan.qty, 'rate': (get_plan_rate(plan.plan, plan.qty, customer) * prorate_factor)}) - if prorate: - prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start) - - for item in item_details: - item['rate'] = item['rate'] * prorate_factor - - return item_details + return items def process(self): """ @@ -329,7 +330,7 @@ class Subscription(Document): 2. Change the `Subscription` status to 'Past Due Date' 3. Change the `Subscription` status to 'Cancelled' """ - if getdate(nowdate()) > getdate(self.current_invoice_end) and not self.has_outstanding_invoice(): + if getdate(nowdate()) > getdate(self.current_invoice_end) or (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and not self.has_outstanding_invoice(): self.generate_invoice() if self.current_invoice_is_past_due(): self.status = 'Past Due Date' @@ -363,7 +364,7 @@ class Subscription(Document): else: if self.is_not_outstanding(current_invoice): self.status = 'Active' - self.update_subscription_period(nowdate()) + self.update_subscription_period(add_days(self.current_invoice_end, 1)) else: self.set_status_grace_period() diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 47efa45429a..c42b8e824b9 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -15,6 +15,7 @@ def create_plan(): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name' plan.item = '_Test Non Stock Item' + plan.price_determination = "Fixed rate" plan.cost = 900 plan.billing_interval = 'Month' plan.billing_interval_count = 1 @@ -24,6 +25,7 @@ def create_plan(): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name 2' plan.item = '_Test Non Stock Item' + plan.price_determination = "Fixed rate" plan.cost = 1999 plan.billing_interval = 'Month' plan.billing_interval_count = 1 @@ -33,6 +35,7 @@ def create_plan(): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name 3' plan.item = '_Test Non Stock Item' + plan.price_determination = "Fixed rate" plan.cost = 1999 plan.billing_interval = 'Day' plan.billing_interval_count = 14 @@ -58,7 +61,7 @@ class TestSubscription(unittest.TestCase): subscription.subscriber = '_Test Customer' subscription.trial_period_start = nowdate() subscription.trial_period_end = add_days(nowdate(), 30) - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() self.assertEqual(subscription.trial_period_start, nowdate()) @@ -73,7 +76,7 @@ class TestSubscription(unittest.TestCase): def test_create_subscription_without_trial_with_correct_period(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() self.assertEqual(subscription.trial_period_start, None) @@ -91,7 +94,7 @@ class TestSubscription(unittest.TestCase): subscription.subscriber = '_Test Customer' subscription.trial_period_end = nowdate() subscription.trial_period_start = add_days(nowdate(), 30) - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) self.assertRaises(frappe.ValidationError, subscription.save) subscription.delete() @@ -101,8 +104,8 @@ class TestSubscription(unittest.TestCase): subscription.subscriber = '_Test Customer' subscription.trial_period_end = nowdate() subscription.trial_period_start = add_days(nowdate(), 30) - subscription.append('plans', {'plan': '_Test Plan Name'}) - subscription.append('plans', {'plan': '_Test Plan Name 3'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) + subscription.append('plans', {'plan': '_Test Plan Name 3', 'qty': 1}) self.assertRaises(frappe.ValidationError, subscription.save) subscription.delete() @@ -111,7 +114,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' subscription.start = '2018-01-01' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.insert() self.assertEqual(subscription.status, 'Active') @@ -127,7 +130,7 @@ class TestSubscription(unittest.TestCase): def test_status_goes_back_to_active_after_invoice_is_paid(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -144,7 +147,7 @@ class TestSubscription(unittest.TestCase): subscription.process() self.assertEqual(subscription.status, 'Active') - self.assertEqual(subscription.current_invoice_start, nowdate()) + self.assertEqual(subscription.current_invoice_start, add_months(subscription.start, 1)) self.assertEqual(len(subscription.invoices), 1) subscription.delete() @@ -157,7 +160,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -180,7 +183,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -198,7 +201,7 @@ class TestSubscription(unittest.TestCase): def test_subscription_invoice_days_until_due(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.days_until_due = 10 subscription.start = add_months(nowdate(), -1) subscription.insert() @@ -216,7 +219,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -240,7 +243,7 @@ class TestSubscription(unittest.TestCase): def test_subscription_remains_active_during_invoice_period(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.process() # no changes expected @@ -266,7 +269,7 @@ class TestSubscription(unittest.TestCase): def test_subscription_cancelation(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() @@ -282,7 +285,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() self.assertEqual(subscription.status, 'Active') @@ -317,7 +320,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() invoice = subscription.get_current_invoice() @@ -337,7 +340,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() @@ -361,7 +364,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -395,7 +398,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -432,7 +435,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -450,8 +453,9 @@ class TestSubscription(unittest.TestCase): subscription.process() self.assertEqual(subscription.status, 'Active') + # A new invoice is generated subscription.process() - self.assertEqual(subscription.status, 'Active') + self.assertEqual(subscription.status, 'Past Due Date') settings.cancel_after_grace = default_grace_period_action settings.save() @@ -460,7 +464,7 @@ class TestSubscription(unittest.TestCase): def test_restart_active_subscription(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() self.assertRaises(frappe.ValidationError, subscription.restart_subscription) @@ -471,7 +475,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' subscription.additional_discount_percentage = 10 - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() @@ -486,7 +490,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' subscription.additional_discount_amount = 11 - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js index f5ea8047c6a..aaa32cfe7ef 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js @@ -1,2 +1,9 @@ // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt + +frappe.ui.form.on('Subscription Plan', { + price_determination: function(frm) { + frm.toggle_reqd("cost", frm.doc.price_determination === 'Fixed rate'); + frm.toggle_reqd("price_list", frm.doc.price_determination === 'Based on price list'); + } +}); \ No newline at end of file diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json index ab58e7c3c6f..453521d04de 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json @@ -15,6 +15,7 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -42,42 +43,11 @@ "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": "item", - "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", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "unique": 1 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -110,10 +80,172 @@ }, { "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", + "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", + "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": 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": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "price_determination", + "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": "Price Determination", + "length": 0, + "no_copy": 0, + "options": "\nFixed rate\nBased on price list", + "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_7", + "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, + "depends_on": "eval:doc.price_determination==\"Fixed rate\"", "fieldname": "cost", "fieldtype": "Currency", "hidden": 0, @@ -133,7 +265,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -141,6 +273,72 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.price_determination==\"Based on price list\"", + "fieldname": "price_list", + "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": "Price List", + "length": 0, + "no_copy": 0, + "options": "Price List", + "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_11", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -174,6 +372,38 @@ }, { "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, @@ -204,6 +434,134 @@ "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": "payment_plan_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": "Payment Plan", + "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": "payment_plan_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Plan", + "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_16", + "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": "payment_gateway", + "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": "Payment Gateway", + "length": 0, + "no_copy": 0, + "options": "Payment Gateway Account", + "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, @@ -216,7 +574,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-02-27 09:12:58.330140", + "modified": "2018-06-20 16:59:54.082358", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan", @@ -225,7 +583,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -244,7 +601,7 @@ "write": 1 } ], - "quick_entry": 1, + "quick_entry": 0, "read_only": 0, "read_only_onload": 0, "show_name_in_global_search": 0, diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py index 4b8c8fcedd7..d3fef6023ba 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from erpnext.utilities.product import get_price class SubscriptionPlan(Document): def validate(self): @@ -13,3 +14,21 @@ class SubscriptionPlan(Document): def validate_interval_count(self): if self.billing_interval_count < 1: frappe.throw('Billing Interval Count cannot be less than 1') + +@frappe.whitelist() +def get_plan_rate(plan, quantity=1, customer=None): + plan = frappe.get_doc("Subscription Plan", plan) + if plan.price_determination == "Fixed rate": + return plan.cost + + elif plan.price_determination == "Based on price list": + if customer: + customer_group = frappe.db.get_value("Customer", customer, "customer_group") + else: + customer_group = None + + price = get_price(item_code=plan.item, price_list=plan.price_list, customer_group=customer_group, company=None, qty=quantity) + if not price: + return 0 + else: + return price.price_list_rate diff --git a/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json b/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json index c1129233974..ca54a167f59 100644 --- a/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json +++ b/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json @@ -14,6 +14,39 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "qty", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "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, @@ -55,7 +88,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-02-25 07:35:07.736146", + "modified": "2018-06-20 15:35:13.514699", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan Detail", diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index c71ecf45f61..204eceb4f48 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -11,6 +11,8 @@ def execute(filters=None): period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, filters.periodicity, company=filters.company) + currency = filters.presentation_currency or frappe.db.get_value("Company", filters.company, "default_currency") + asset = get_data(filters.company, "Asset", "Debit", period_list, only_current_fiscal_year=False, filters=filters, accumulated_values=filters.accumulated_values) @@ -24,7 +26,7 @@ def execute(filters=None): accumulated_values=filters.accumulated_values) provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity, - period_list, filters.company) + period_list, filters.company, currency) message, opening_balance = check_opening_balance(asset, liability, equity) @@ -37,7 +39,7 @@ def execute(filters=None): "account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'", "account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'", "warn_if_negative": True, - "currency": frappe.db.get_value("Company", filters.company, "default_currency") + "currency": currency } for period in period_list: unclosed[period.key] = opening_balance @@ -58,12 +60,12 @@ def execute(filters=None): return columns, data, message, chart -def get_provisional_profit_loss(asset, liability, equity, period_list, company, consolidated=False): +def get_provisional_profit_loss(asset, liability, equity, period_list, company, currency=None, consolidated=False): provisional_profit_loss = {} total_row = {} if asset and (liability or equity): total = total_row_total=0 - currency = frappe.db.get_value("Company", company, "default_currency") + currency = currency or frappe.db.get_value("Company", company, "default_currency") total_row = { "account_name": "'" + _("Total (Credit)") + "'", "account": "'" + _("Total (Credit)") + "'", diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index f8eec9ea707..46417b8dd11 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -210,14 +210,15 @@ def prepare_data(accounts, balance_must_be, period_list, company_currency): has_value = False total = 0 row = frappe._dict({ - "account_name": _(d.account_name), "account": _(d.name), "parent_account": _(d.parent_account), "indent": flt(d.indent), "year_start_date": year_start_date, "year_end_date": year_end_date, "currency": company_currency, - "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1) + "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be=="Debit" else -1), + "account_name": ('{} - {}'.format(_(d.account_number), _(d.account_name)) + if d.account_number else _(d.account_name)) }) for period in period_list: if d.get(period.key) and balance_must_be == "Credit": @@ -281,8 +282,9 @@ def add_total_row(out, root_type, balance_must_be, period_list, company_currency def get_accounts(company, root_type): - return frappe.db.sql( - """select name, parent_account, lft, rgt, root_type, report_type, account_name from `tabAccount` + return frappe.db.sql(""" + select name, account_number, parent_account, lft, rgt, root_type, report_type, account_name + from `tabAccount` where company=%s and root_type=%s order by lft""", (company, root_type), as_dict=True) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 0663228b561..5ba0bde7735 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -80,6 +80,7 @@ frappe.query_reports["General Ledger"] = { "label": __("Party"), "fieldtype": "MultiSelect", get_data: function() { + if (!frappe.query_report.filters) return; var party_type = frappe.query_report.get_filter_value('party_type'); var parties = frappe.query_report.get_filter_value('party'); if(!party_type) return; diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index ccd3a82f7d3..7df877f38e3 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -157,6 +157,9 @@ def get_delivery_notes_against_sales_order(item_list): return so_dn_map +def get_deducted_taxes(): + return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'") + def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): import json @@ -176,9 +179,10 @@ def get_tax_accounts(item_list, columns, company_currency, if doctype == "Purchase Invoice": conditions = " and category in ('Total', 'Valuation and Total') and base_tax_amount_after_discount_amount != 0" + deducted_tax = get_deducted_taxes() tax_details = frappe.db.sql(""" select - parent, description, item_wise_tax_detail, + name, parent, description, item_wise_tax_detail, charge_type, base_tax_amount_after_discount_amount from `tab%s` where @@ -190,7 +194,7 @@ def get_tax_accounts(item_list, columns, company_currency, """ % (tax_doctype, '%s', ', '.join(['%s']*len(invoice_item_row)), conditions), tuple([doctype] + list(invoice_item_row))) - for parent, description, item_wise_tax_detail, charge_type, tax_amount in tax_details: + for name, parent, description, item_wise_tax_detail, charge_type, tax_amount in tax_details: description = handle_html(description) if description not in tax_columns and tax_amount: # as description is text editor earlier and markup can break the column convention in reports @@ -219,9 +223,13 @@ def get_tax_accounts(item_list, columns, company_currency, item_tax_amount = flt((tax_amount * d.base_net_amount) / item_net_amount) \ if item_net_amount else 0 if item_tax_amount: + tax_amount = flt(item_tax_amount, tax_amount_precision) + tax_amount = (tax_amount * -1 + if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_amount) + itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ "tax_rate": tax_rate, - "tax_amount": flt(item_tax_amount, tax_amount_precision) + "tax_amount": tax_amount }) except ValueError: diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index 33a29484291..80b50b92c36 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -59,20 +59,21 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "Check" } ], - "formatter": function(row, cell, value, columnDef, dataContext, default_formatter) { - if (columnDef.df.fieldname=="account") { - value = dataContext.account_name; + "formatter": function(value, row, column, data, default_formatter) { + if (column.fieldname=="account") { + value = data.account_name; - columnDef.df.link_onclick = - "frappe.query_reports['Profitability Analysis'].open_profit_and_loss_statement(" + JSON.stringify(dataContext) + ")"; - columnDef.df.is_tree = true; + column.link_onclick = + "frappe.query_reports['Profitability Analysis'].open_profit_and_loss_statement(" + JSON.stringify(data) + ")"; + column.is_tree = true; } - value = default_formatter(row, cell, value, columnDef, dataContext); + value = default_formatter(value, row, column, data); - if (!dataContext.parent_account && dataContext.based_on != 'project') { + if (!data.parent_account && data.based_on != 'project') { + value = $(`${value}`); var $value = $(value).css("font-weight", "bold"); - if (dataContext.warn_if_negative && dataContext[columnDef.df.fieldname] < 0) { + if (data.warn_if_negative && data[column.fieldname] < 0) { $value.addClass("text-danger"); } diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 8c55df5c4aa..657523b962a 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -51,8 +51,9 @@ def validate_filters(filters): filters.to_date = filters.year_end_date def get_data(filters): - accounts = frappe.db.sql("""select name, parent_account, account_name, account_number, root_type, report_type, lft, rgt - from `tabAccount` where company=%s order by account_name, lft""", filters.company, as_dict=True) + accounts = frappe.db.sql(""" + select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt + from `tabAccount` where company=%s order by lft""", filters.company, as_dict=True) company_currency = erpnext.get_company_currency(filters.company) if not accounts: @@ -162,8 +163,6 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, total_row["credit"] += d["credit"] total_row["opening_debit"] += d["opening_debit"] total_row["opening_credit"] += d["opening_credit"] - total_row["closing_debit"] += (d["opening_debit"] + d["debit"]) - total_row["closing_credit"] += (d["opening_credit"] + d["credit"]) return total_row @@ -179,16 +178,19 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr if not (accounts[0].account_number is None): accounts = tmpaccnt + total_row["closing_debit"] = total_row["closing_credit"] = 0 + for d in accounts: has_value = False row = { - "account_name": d.account_name, "account": d.name, "parent_account": d.parent_account, "indent": d.indent, "from_date": filters.from_date, "to_date": filters.to_date, - "currency": company_currency + "currency": company_currency, + "account_name": ('{} - {}'.format(d.account_number, d.account_name) + if d.account_number else d.account_name) } prepare_opening_and_closing(d) @@ -203,6 +205,10 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr row["has_value"] = has_value data.append(row) + if not d.parent_account: + total_row["closing_debit"] += (d["debit"] - d["credit"]) if (d["debit"] - d["credit"]) > 0 else 0 + total_row["closing_credit"] += abs(d["debit"] - d["credit"]) if (d["debit"] - d["credit"]) < 0 else 0 + data.extend([{},total_row]) return data diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index c430b5638f5..599afe00644 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -1879,7 +1879,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-07-27 06:30:25.506194", + "modified": "2018-07-30 08:30:37.355749", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 985d13e7455..e475a5efec9 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -115,7 +115,7 @@ class Asset(AccountsController): from_date = self.available_for_use_date if number_of_pending_depreciations: - next_depr_date = getdate(add_months(self.available_for_use_date, + next_depr_date = getdate(add_months(self.available_for_use_date, number_of_pending_depreciations * 12)) if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1 and getdate(d.depreciation_start_date) < next_depr_date): @@ -315,14 +315,14 @@ class Asset(AccountsController): elif self.docstatus == 1: status = "Submitted" - idx = self.get_default_finance_book_idx() or 0 - - expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life - value_after_depreciation = self.finance_books[idx].value_after_depreciation - if self.journal_entry_for_scrap: status = "Scrapped" elif self.finance_books: + idx = self.get_default_finance_book_idx() or 0 + + expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life + value_after_depreciation = self.finance_books[idx].value_after_depreciation + if flt(value_after_depreciation) <= expected_value_after_useful_life: status = "Fully Depreciated" elif flt(value_after_depreciation) < flt(self.gross_purchase_amount): diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 16e7749df05..3c0d44e5e98 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -3671,7 +3671,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-07-27 07:49:53.131408", + "modified": "2018-07-30 08:35:10.345286", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 8f7bb2bcbc1..6576bcfc030 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -2779,7 +2779,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-07-17 02:45:48.616334", + "modified": "2018-07-30 08:36:34.701682", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/config/healthcare.py b/erpnext/config/healthcare.py index ae36f9c93c8..e55f7364819 100644 --- a/erpnext/config/healthcare.py +++ b/erpnext/config/healthcare.py @@ -38,6 +38,11 @@ def get_data(): "type": "doctype", "name": "Clinical Procedure", "label": _("Clinical Procedure"), + }, + { + "type": "doctype", + "name": "Inpatient Record", + "label": _("Inpatient Record"), } ] }, @@ -167,6 +172,11 @@ def get_data(): "type": "doctype", "name": "Clinical Procedure Template", "label": _("Clinical Procedure Template"), + }, + { + "type": "doctype", + "name": "Healthcare Service Unit Type", + "label": _("Healthcare Service Unit Type") } ] } diff --git a/erpnext/config/erpnext_integrations.py b/erpnext/config/integrations.py similarity index 100% rename from erpnext/config/erpnext_integrations.py rename to erpnext/config/integrations.py diff --git a/erpnext/demo/user/education.py b/erpnext/demo/user/education.py index 4fc9c6abc55..fc31176e1e5 100644 --- a/erpnext/demo/user/education.py +++ b/erpnext/demo/user/education.py @@ -51,12 +51,12 @@ def enroll_random_student(current_date): def assign_student_group(student, student_name, program, courses, batch): course_list = [d["course"] for d in courses] - for d in frappe.get_list("Student Group", fields=("name"), filters={"program": program, "course":("in", course_list)}): + for d in frappe.get_list("Student Group", fields=("name"), filters={"program": program, "course":("in", course_list), "disabled": 0}): student_group = frappe.get_doc("Student Group", d.name) student_group.append("students", {"student": student, "student_name": student_name, "group_roll_number":len(student_group.students)+1, "active":1}) student_group.save() - student_batch = frappe.get_list("Student Group", fields=("name"), filters={"program": program, "group_based_on":"Batch", "batch":batch})[0] + student_batch = frappe.get_list("Student Group", fields=("name"), filters={"program": program, "group_based_on":"Batch", "batch":batch, "disabled": 0})[0] student_batch_doc = frappe.get_doc("Student Group", student_batch.name) student_batch_doc.append("students", {"student": student, "student_name": student_name, "group_roll_number":len(student_batch_doc.students)+1, "active":1}) @@ -65,7 +65,7 @@ def assign_student_group(student, student_name, program, courses, batch): def mark_student_attendance(current_date): status = ["Present", "Absent"] - for d in frappe.db.get_list("Student Group", filters={"group_based_on": "Batch"}): + for d in frappe.db.get_list("Student Group", filters={"group_based_on": "Batch", "disabled": 0}): students = get_student_group_students(d.name) for stud in students: make_attendance_records(stud.student, stud.student_name, status[weighted_choice([9,4])], None, d.name, current_date) @@ -77,7 +77,7 @@ def make_fees(): def make_assessment_plan(date): for d in range(1,4): - random_group = get_random("Student Group", {"group_based_on": "Course"}, True) + random_group = get_random("Student Group", {"group_based_on": "Course", "disabled": 0}, True) doc = frappe.new_doc("Assessment Plan") doc.student_group = random_group.name doc.course = random_group.course diff --git a/erpnext/education/api.py b/erpnext/education/api.py index 8571bf9381b..30d55880732 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -88,16 +88,14 @@ def make_attendance_records(student, student_name, status, course_schedule=None, :param course_schedule: Course Schedule. :param status: Status (Present/Absent) """ - student_attendance_list = frappe.get_list("Student Attendance", fields = ['name'], filters = { + student_attendance = frappe.get_doc({ + "doctype": "Student Attendance", "student": student, "course_schedule": course_schedule, "student_group": student_group, "date": date }) - - if student_attendance_list: - student_attendance = frappe.get_doc("Student Attendance", student_attendance_list[0]) - else: + if not student_attendance: student_attendance = frappe.new_doc("Student Attendance") student_attendance.student = student student_attendance.student_name = student_name diff --git a/erpnext/education/doctype/education_settings/education_settings.json b/erpnext/education/doctype/education_settings/education_settings.json index a0b8e99f48e..c1eaa112848 100644 --- a/erpnext/education/doctype/education_settings/education_settings.json +++ b/erpnext/education/doctype/education_settings/education_settings.json @@ -195,6 +195,38 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "description": "If enabled, field Academic Term will be Mandatory in Program Enrollment Tool.", + "fieldname": "academic_term_reqd", + "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": "Make Academic Term Mandatory", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -267,7 +299,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-11-28 15:45:30.324324", + "modified": "2018-07-26 04:43:35.406690", "modified_by": "Administrator", "module": "Education", "name": "Education Settings", diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.js b/erpnext/education/doctype/fee_schedule/fee_schedule.js index c4fff77d893..13383312a8c 100644 --- a/erpnext/education/doctype/fee_schedule/fee_schedule.js +++ b/erpnext/education/doctype/fee_schedule/fee_schedule.js @@ -31,7 +31,8 @@ frappe.ui.form.on('Fee Schedule', { return { "program": frm.doc.program, "academic_term": frm.doc.academic_term, - "academic_year": frm.doc.academic_year + "academic_year": frm.doc.academic_year, + "disabled": 0 }; }); frappe.realtime.on("fee_schedule_progress", function(data) { diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.json b/erpnext/education/doctype/program_enrollment/program_enrollment.json index 817c4bc7eb3..23da14948a2 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.json +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.json @@ -693,7 +693,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-05-16 22:43:04.881120", + "modified": "2018-07-26 04:44:03.781418", "modified_by": "Administrator", "module": "Education", "name": "Program Enrollment", diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py index 0f9bb96adb1..320a58a9249 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -26,7 +26,6 @@ class ProgramEnrollment(Document): "student": self.student, "program": self.program, "academic_year": self.academic_year, - "academic_term": self.academic_term, "docstatus": ("<", 2), "name": ("!=", self.name) }) @@ -86,7 +85,6 @@ def get_program_courses(doctype, txt, searchfield, start, page_len, filters): "program": filters['program'] }) - @frappe.whitelist() def get_students(doctype, txt, searchfield, start, page_len, filters): if not filters.get("academic_term"): diff --git a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.js b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.js index 2e54a2febf6..06d75981940 100644 --- a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.js +++ b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.js @@ -5,6 +5,9 @@ frappe.ui.form.on("Program Enrollment Tool", { setup: function(frm) { frm.add_fetch("student", "title", "student_name"); frm.add_fetch("student_applicant", "title", "student_name"); + if(frm.doc.__onload && frm.doc.__onload.academic_term_reqd) { + frm.toggle_reqd("academic_term", true); + } }, "refresh": function(frm) { diff --git a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.json b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.json index d611a6f2706..35ad98d120b 100644 --- a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.json +++ b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.json @@ -513,7 +513,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-01-02 11:59:40.230689", + "modified": "2018-07-26 04:44:13.232146", "modified_by": "Administrator", "module": "Education", "name": "Program Enrollment Tool", diff --git a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py index 0b134493dd8..db23ac744d8 100644 --- a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py +++ b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py @@ -7,8 +7,13 @@ import frappe from frappe import _ from frappe.model.document import Document from erpnext.education.api import enroll_student +from frappe.utils import cint class ProgramEnrollmentTool(Document): + def onload(self): + academic_term_reqd = cint(frappe.db.get_single_value('Education Settings', 'academic_term_reqd')) + self.set_onload("academic_term_reqd", academic_term_reqd) + def get_students(self): students = [] if not self.get_students_from: diff --git a/erpnext/education/doctype/student_attendance/student_attendance.json b/erpnext/education/doctype/student_attendance/student_attendance.json index f7030537bd4..23e10e68c58 100644 --- a/erpnext/education/doctype/student_attendance/student_attendance.json +++ b/erpnext/education/doctype/student_attendance/student_attendance.json @@ -3,7 +3,7 @@ "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, - "autoname": "SA.######", + "autoname": "", "beta": 0, "creation": "2015-11-05 15:20:23.045996", "custom": 0, @@ -40,7 +40,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, - "search_index": 0, + "search_index": 1, "set_only_once": 0, "translatable": 0, "unique": 0 @@ -103,7 +103,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, - "search_index": 0, + "search_index": 1, "set_only_once": 0, "translatable": 0, "unique": 0 @@ -247,7 +247,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-16 22:43:27.329881", + "modified": "2018-07-27 10:48:22.301531", "modified_by": "Administrator", "module": "Education", "name": "Student Attendance", diff --git a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js index df6d13250a8..cc9607da19f 100644 --- a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js +++ b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js @@ -7,7 +7,8 @@ frappe.ui.form.on('Student Attendance Tool', { frm.set_query("student_group", function() { return { "filters": { - "group_based_on": frm.doc.group_based_on + "group_based_on": frm.doc.group_based_on, + "disabled": 0 } }; }); diff --git a/erpnext/education/doctype/student_group/student_group.json b/erpnext/education/doctype/student_group/student_group.json index 37a611b33da..0af156517d5 100644 --- a/erpnext/education/doctype/student_group/student_group.json +++ b/erpnext/education/doctype/student_group/student_group.json @@ -293,6 +293,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "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, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -459,7 +490,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-11-10 19:09:37.370864", + "modified": "2018-07-26 04:17:10.836912", "modified_by": "Administrator", "module": "Education", "name": "Student Group", diff --git a/erpnext/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py b/erpnext/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py index 19136ea9c9d..c2ac0d7d7be 100644 --- a/erpnext/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py +++ b/erpnext/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py @@ -99,7 +99,7 @@ def get_guardian_map(student_list): def get_student_roll_no(academic_year, program, batch): student_group = frappe.get_all("Student Group", - filters={"academic_year":academic_year, "program":program, "batch":batch}) + filters={"academic_year":academic_year, "program":program, "batch":batch, "disabled": 0}) if student_group: roll_no_dict = dict(frappe.db.sql('''select student, group_roll_number from `tabStudent Group Student` where parent=%s''', (student_group[0].name))) diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py index af15cf58d09..c65e3cefeeb 100644 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py +++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py @@ -145,11 +145,11 @@ class GoCardlessSettings(Document): if self.flags.status_changed_to == "Completed": status = 'Completed' - if self.data.reference_doctype and self.data.reference_docname: + if 'reference_doctype' in self.data and 'reference_docname' in self.data: custom_redirect_to = None try: - custom_redirect_to = frappe.get_doc(self.data.reference_doctype, - self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) + custom_redirect_to = frappe.get_doc(self.data.get('reference_doctype'), + self.data.get('reference_docname')).run_method("on_payment_authorized", self.flags.status_changed_to) except Exception: frappe.log_error(frappe.get_traceback()) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index fd4f4986b25..82560979bed 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -58,8 +58,7 @@ class ShopifySettings(Document): d.raise_for_status() self.update_webhook_table(method, d.json()) except Exception as e: - make_shopify_log(status="Warning", method="register_webhooks", - message=e.message, exception=False) + make_shopify_log(status="Warning", message=e.message, exception=False) def unregister_webhooks(self): session = get_request_session() diff --git a/erpnext/erpnext_integrations/stripe_integration.py b/erpnext/erpnext_integrations/stripe_integration.py new file mode 100644 index 00000000000..a35ca28e0a3 --- /dev/null +++ b/erpnext/erpnext_integrations/stripe_integration.py @@ -0,0 +1,53 @@ +# -*- 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.integrations.utils import create_request_log +import stripe + +def create_stripe_subscription(gateway_controller, data): + stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) + stripe_settings.data = frappe._dict(data) + + stripe.api_key = stripe_settings.get_password(fieldname="secret_key", raise_exception=False) + stripe.default_http_client = stripe.http_client.RequestsClient() + + try: + stripe_settings.integration_request = create_request_log(stripe_settings.data, "Host", "Stripe") + stripe_settings.payment_plans = frappe.get_doc("Payment Request", stripe_settings.data.reference_docname).subscription_plans + return create_subscription_on_stripe(stripe_settings) + + except Exception: + frappe.log_error(frappe.get_traceback()) + return{ + "redirect_to": frappe.redirect_to_message(_('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")), + "status": 401 + } + + +def create_subscription_on_stripe(stripe_settings): + items = [] + for payment_plan in stripe_settings.payment_plans: + plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "payment_plan_id") + items.append({"plan": plan, "quantity": payment_plan.qty}) + + try: + customer = stripe.Customer.create(description=stripe_settings.data.payer_name, email=stripe_settings.data.payer_email, source=stripe_settings.data.stripe_token_id) + subscription = stripe.Subscription.create(customer=customer, items=items) + + if subscription.status == "active": + stripe_settings.integration_request.db_set('status', 'Completed', update_modified=False) + stripe_settings.flags.status_changed_to = "Completed" + + else: + stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False) + frappe.log_error('Subscription N°: ' + subscription.id, 'Stripe Payment not completed') + + except Exception: + stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False) + frappe.log_error(frappe.get_traceback()) + + return stripe_settings.finalize_request() \ No newline at end of file diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json index c687393b329..c316ba71a21 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json @@ -15,6 +15,41 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "patient.inpatient_record", + "fieldname": "inpatient_record", + "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 Record", + "length": 0, + "no_copy": 0, + "options": "Inpatient Record", + "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, @@ -47,6 +82,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -79,10 +115,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", "hidden": 0, @@ -111,6 +149,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -142,6 +181,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -173,6 +213,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -205,6 +246,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -237,6 +279,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -267,6 +310,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -299,6 +343,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -331,6 +376,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -363,6 +409,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -394,6 +441,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -426,6 +474,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -457,6 +506,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -488,6 +538,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -520,6 +571,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -553,6 +605,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -585,6 +638,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -617,6 +671,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -659,7 +714,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-04 11:23:32.884076", + "modified": "2018-07-12 19:18:30.400534", "modified_by": "Administrator", "module": "Healthcare", "name": "Clinical Procedure", diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js index e9665151d6f..8480a526b44 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js @@ -17,6 +17,7 @@ frappe.ui.form.on('Healthcare Service Unit', { }, refresh: function(frm) { frm.trigger("set_root_readonly"); + frm.set_df_property("service_unit_type", "reqd", 1); frm.add_custom_button(__("Healthcare Service Unit Tree"), function() { frappe.set_route("Tree", "Healthcare Service Unit"); }); @@ -35,8 +36,12 @@ frappe.ui.form.on('Healthcare Service Unit', { } }, is_group: function(frm) { - if(frm.doc.is_group){ + if(frm.doc.is_group == 1){ frm.set_value("allow_appointments", false); + frm.set_df_property("service_unit_type", "reqd", 0); + } + else{ + frm.set_df_property("service_unit_type", "reqd", 1); } } }); 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 dbb5b20fd6b..945817c8d39 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json @@ -15,6 +15,7 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -46,6 +47,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 1, "collapsible": 0, @@ -78,10 +80,13 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 1, "collapsible": 0, "columns": 0, + "default": "0", + "depends_on": "eval:doc.inpatient_occupancy != 1 && doc.allow_appointments != 1", "fieldname": "is_group", "fieldtype": "Check", "hidden": 0, @@ -109,11 +114,48 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.is_group != 1", + "fieldname": "service_unit_type", + "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": "Service Unit Type", + "length": 0, + "no_copy": 0, + "options": "Healthcare Service Unit Type", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 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, - "depends_on": "eval:doc.is_group != 1", + "default": "0", + "depends_on": "eval:doc.is_group != 1 && doc.inpatient_occupancy != 1", + "fetch_from": "service_unit_type.allow_appointments", "fieldname": "allow_appointments", "fieldtype": "Check", "hidden": 0, @@ -125,12 +167,12 @@ "in_standard_filter": 0, "label": "Allow Appointments", "length": 0, - "no_copy": 0, + "no_copy": 1, "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, @@ -141,11 +183,14 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, - "bold": 1, + "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:doc.is_group != 1 && doc.allow_appointments == 1", + "default": "0", + "depends_on": "eval:doc.is_group != 1 && doc.allow_appointments == 1 && doc.inpatient_occupany != 1", + "fetch_from": "service_unit_type.overlap_appointments", "fieldname": "overlap_appointments", "fieldtype": "Check", "hidden": 0, @@ -157,12 +202,12 @@ "in_standard_filter": 0, "label": "Allow Overlap", "length": 0, - "no_copy": 0, + "no_copy": 1, "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, @@ -173,6 +218,76 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "default": "0", + "depends_on": "eval:doc.allow_appointments != 1 && doc.is_group != 1", + "fetch_from": "service_unit_type.inpatient_occupancy", + "fieldname": "inpatient_occupancy", + "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": "Inpatient Occupancy", + "length": 0, + "no_copy": 1, + "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": "0", + "depends_on": "eval:doc.inpatient_occupancy == 1", + "fieldname": "occupied", + "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": "Occupied", + "length": 0, + "no_copy": 1, + "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": 1, "collapsible": 0, @@ -189,7 +304,7 @@ "in_standard_filter": 0, "label": "Warehouse", "length": 0, - "no_copy": 0, + "no_copy": 1, "options": "Warehouse", "permlevel": 0, "precision": "", @@ -206,6 +321,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -238,6 +354,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -269,6 +386,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -300,6 +418,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -341,7 +460,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-04 11:20:16.942603", + "modified": "2018-07-17 17:40:18.867327", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Service Unit", @@ -417,4 +536,4 @@ "title_field": "healthcare_service_unit_name", "track_changes": 1, "track_seen": 0 -} +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py index 21b0e6e7278..89adbf849a5 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py @@ -14,8 +14,9 @@ class HealthcareServiceUnit(NestedSet): self.validate_one_root() def validate(self): - if self.is_group: - self.allow_appointments = False - self.overlap_appointments = False - elif not self.allow_appointments: - self.overlap_appointments = False + if self.is_group == 1: + self.allow_appointments = 0 + self.overlap_appointments = 0 + self.inpatient_occupancy = 0 + elif self.allow_appointments != 1: + self.overlap_appointments = 0 diff --git a/erpnext/hr/doctype/salary_structure_employee/__init__.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/__init__.py similarity index 100% rename from erpnext/hr/doctype/salary_structure_employee/__init__.py rename to erpnext/healthcare/doctype/healthcare_service_unit_type/__init__.py diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js new file mode 100644 index 00000000000..84255b2930b --- /dev/null +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js @@ -0,0 +1,119 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Healthcare Service Unit Type', { + service_unit_type: function(frm) { + set_item_details(frm); + if(!frm.doc.__islocal){ + frm.doc.change_in_item = 1; + } + }, + is_billable: function(frm) { + set_item_details(frm); + }, + refresh: function(frm) { + frm.set_df_property("item_code", "read_only", frm.doc.__islocal ? 0 : 1); + if(!frm.doc.__islocal) { + frm.add_custom_button(__('Change Item Code'), function() { + change_item_code(cur_frm,frm.doc); + } ); + if(frm.doc.disabled == 1){ + frm.add_custom_button(__('Enable'), function() { + enable(cur_frm); + } ); + } + else{ + frm.add_custom_button(__('Disable'), function() { + disable(cur_frm); + } ); + } + } + }, + rate: function(frm) { + if(!frm.doc.__islocal){ + frm.doc.change_in_item = 1; + } + }, + item_group: function(frm) { + if(!frm.doc.__islocal){ + frm.doc.change_in_item = 1; + } + }, + description: function(frm) { + if(!frm.doc.__islocal){ + frm.doc.change_in_item = 1; + } + } +}); + +var disable = function(frm){ + var doc = frm.doc; + frappe.call({ + method: "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.disable_enable", + args: {status: 1, doc_name: doc.name, item: doc.item, is_billable: doc.is_billable}, + callback: function(){ + cur_frm.reload_doc(); + } + }); +}; + +var enable = function(frm){ + var doc = frm.doc; + frappe.call({ + method: "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.disable_enable", + args: {status: 0, doc_name: doc.name, item: doc.item, is_billable: doc.is_billable}, + callback: function(){ + cur_frm.reload_doc(); + } + }); +}; + +var change_item_code = function(frm, doc){ + var d = new frappe.ui.Dialog({ + title:__("Change Item Code"), + fields:[ + { + "fieldtype": "Data", + "label": "Item Code", + "fieldname": "Item Code", + reqd:1 + }, + { + "fieldtype": "Button", + "label": __("Change Code"), + click: function() { + var values = d.get_values(); + if(!values) + return; + change_item_code_from_unit_type(values["Item Code"], doc); + d.hide(); + } + } + ] + }); + d.show(); + d.set_values({ + 'Item Code': frm.doc.item_code + }); + + var change_item_code_from_unit_type = function(item_code, doc){ + frappe.call({ + "method": "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.change_item_code", + "args": {item: doc.item, item_code: item_code, doc_name: doc.name}, + callback: function () { + frm.reload_doc(); + } + }); + }; +}; + +var set_item_details = function(frm) { + if(frm.doc.service_unit_type && frm.doc.is_billable == 1){ + if(!frm.doc.item_code) + frm.set_value("item_code", frm.doc.service_unit_type); + if(!frm.doc.description) + frm.set_value("description", frm.doc.service_unit_type); + if(!frm.doc.item_group) + frm.set_value("item_group", 'Services'); + } +}; 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 new file mode 100644 index 00000000000..6394ce7361a --- /dev/null +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json @@ -0,0 +1,554 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:service_unit_type", + "beta": 0, + "creation": "2018-07-11 16:47:51.414675", + "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": "service_unit_type", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Service Unit Type", + "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": 1, + "collapsible": 0, + "columns": 0, + "default": "0", + "depends_on": "eval:doc.inpatient_occupancy != 1", + "fieldname": "allow_appointments", + "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 Appointments", + "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": 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, + "default": "0", + "depends_on": "eval:doc.allow_appointments == 1 && doc.inpatient_occupany != 1", + "fieldname": "overlap_appointments", + "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 Overlap", + "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": 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, + "default": "0", + "depends_on": "eval:doc.allow_appointments != 1", + "fieldname": "inpatient_occupancy", + "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": "Inpatient Occupancy", + "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": 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, + "depends_on": "eval:doc.inpatient_occupancy == 1 && doc.allow_appointments != 1", + "fieldname": "is_billable", + "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": "Is Billable", + "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_billable", + "fieldname": "item_details", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Item 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, + "fieldname": "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": "Item", + "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": "item_code", + "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 Code", + "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_group", + "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": "Item Group", + "length": 0, + "no_copy": 0, + "options": "Item Group", + "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": "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, + "fieldname": "column_break_11", + "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": "0", + "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": 1, + "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": "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": "Description", + "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": "change_in_item", + "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": "Change in 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": 0, + "max_attachments": 0, + "modified": "2018-07-13 16:54:59.131606", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare Service Unit Type", + "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": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "service_unit_type", + "track_changes": 0, + "track_seen": 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 new file mode 100644 index 00000000000..e0d380bc5e4 --- /dev/null +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py @@ -0,0 +1,109 @@ +# -*- 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.model.document import Document + +class HealthcareServiceUnitType(Document): + def after_insert(self): + if self.inpatient_occupancy and self.is_billable: + create_item(self) + + def on_trash(self): + if(self.item): + try: + frappe.delete_doc("Item",self.item) + except Exception: + frappe.throw(_("""Not permitted. Please disable the Service Unit Type""")) + + 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): + 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) + + frappe.db.set_value(self.doctype,self.name,"change_in_item",0) + elif(self.is_billable == 0 and self.item): + frappe.db.set_value("Item",self.item,"disabled",1) + self.reload() + +def item_price_exist(doc): + item_price = frappe.db.exists({ + "doctype": "Item Price", + "item_code": doc.item_code}) + if(item_price): + return True + else: + return False + +def updating_item(doc): + frappe.db.sql("""update `tabItem` set item_name=%s, item_group=%s, disabled=0, standard_rate=%s, + description=%s, modified=NOW() where item_code=%s""", + (doc.service_unit_type, doc.item_group , doc.rate, doc.description, doc.item)) + +def create_item(doc): + #insert item + item = frappe.get_doc({ + "doctype": "Item", + "item_code": doc.item_code, + "item_name":doc.service_unit_type, + "item_group": doc.item_group, + "description":doc.description, + "is_sales_item": 1, + "is_service_item": 1, + "is_purchase_item": 0, + "is_stock_item": 0, + "show_in_website": 0, + "is_pro_applicable": 0, + "disabled": 0, + "stock_uom": doc.uom + }).insert(ignore_permissions=True) + + #insert item price + #get item price list to insert item price + if(doc.rate != 0.0): + price_list_name = frappe.db.get_value("Price List", {"selling": 1}) + if(doc.rate): + make_item_price(item.name, price_list_name, doc.rate) + item.standard_rate = doc.rate + else: + make_item_price(item.name, price_list_name, 0.0) + item.standard_rate = 0.0 + item.save(ignore_permissions = True) + #Set item to the Doc + frappe.db.set_value("Healthcare Service Unit Type", doc.name, "item", item.name) + + doc.reload() #refresh the doc after insert. + +def make_item_price(item, price_list_name, item_price): + frappe.get_doc({ + "doctype": "Item Price", + "price_list": price_list_name, + "item_code": item, + "price_list_rate": item_price + }).insert(ignore_permissions=True) + +@frappe.whitelist() +def change_item_code(item, item_code, doc_name): + item_exist = frappe.db.exists({ + "doctype": "Item", + "item_code": item_code}) + if(item_exist): + frappe.throw(_("Code {0} already exist").format(item_code)) + else: + frappe.rename_doc("Item", item, item_code, ignore_permissions = True) + frappe.db.set_value("Healthcare Service Unit Type", doc_name, "item_code", item_code) + +@frappe.whitelist() +def disable_enable(status, doc_name, item, is_billable): + frappe.db.set_value("Healthcare Service Unit Type", doc_name, "disabled", status) + if(is_billable == 1): + frappe.db.set_value("Item", item, "disabled", status) diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js new file mode 100644 index 00000000000..6db8f9e9c1a --- /dev/null +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.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: Healthcare Service Unit Type", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Healthcare Service Unit Type + () => frappe.tests.make('Healthcare Service Unit Type', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py new file mode 100644 index 00000000000..3c5b64fd930 --- /dev/null +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals +import unittest + +class TestHealthcareServiceUnitType(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/__init__.py b/erpnext/healthcare/doctype/inpatient_occupancy/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.json b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json similarity index 53% rename from erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.json rename to erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json index f2295ff2315..2ac498df111 100644 --- a/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.json +++ b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json @@ -1,11 +1,10 @@ { "allow_copy": 0, - "allow_guest_to_view": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, - "autoname": "employee", "beta": 0, - "creation": "2016-07-26 11:53:43.621605", + "creation": "2018-07-12 12:07:36.932333", "custom": 0, "docstatus": 0, "doctype": "DocType", @@ -14,57 +13,57 @@ "engine": "InnoDB", "fields": [ { - "allow_bulk_edit": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "employee", + "fieldname": "service_unit", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_global_search": 1, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Employee", + "label": "Healthcare Service Unit", "length": 0, "no_copy": 0, - "options": "Employee", + "options": "Healthcare Service Unit", "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": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { - "allow_bulk_edit": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Read Only", + "fieldname": "check_in", + "fieldtype": "Datetime", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, + "in_global_search": 0, + "in_list_view": 1, "in_standard_filter": 0, - "label": "Employee Name", + "label": "Check In", "length": 0, "no_copy": 0, - "options": "", "permlevel": 0, "precision": "", "print_hide": 0, @@ -75,137 +74,75 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { - "allow_bulk_edit": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "from_date", - "fieldtype": "Date", + "fieldname": "left", + "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": "From 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": 1, - "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": "to_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "To Date", + "label": "Left", "length": 0, "no_copy": 0, "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, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { - "allow_bulk_edit": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "base", - "fieldtype": "Currency", + "fieldname": "check_out", + "fieldtype": "Datetime", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 0, - "label": "Base", + "label": "Check Out", "length": 0, "no_copy": 0, - "options": "Company:company:default_currency", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "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": "variable", - "fieldtype": "Currency", - "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": "Variable", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 } ], - "has_web_view": 0, + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, @@ -215,10 +152,10 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-05-16 22:43:05.329951", + "modified": "2018-07-17 18:26:46.009878", "modified_by": "Administrator", - "module": "HR", - "name": "Salary Structure Employee", + "module": "Healthcare", + "name": "Inpatient Occupancy", "name_case": "", "owner": "Administrator", "permissions": [], @@ -228,6 +165,6 @@ "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 0, + "track_changes": 1, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.py b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py similarity index 58% rename from erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.py rename to erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py index dfcac3f903b..52de25b4571 100644 --- a/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.py +++ b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# 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.model.document import Document -class SalaryStructureEmployee(Document): +class InpatientOccupancy(Document): pass diff --git a/erpnext/healthcare/doctype/inpatient_record/__init__.py b/erpnext/healthcare/doctype/inpatient_record/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js new file mode 100644 index 00000000000..936c682dca2 --- /dev/null +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js @@ -0,0 +1,185 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Inpatient Record', { + refresh: function(frm) { + if(!frm.doc.__islocal && frm.doc.status == "Admission Scheduled"){ + frm.add_custom_button(__('Admit'), function() { + admit_patient_dialog(frm); + } ); + frm.set_df_property("btn_transfer", "hidden", 1); + } + if(!frm.doc.__islocal && frm.doc.status == "Discharge Scheduled"){ + frm.add_custom_button(__('Discharge'), function() { + discharge_patient(frm); + } ); + frm.set_df_property("btn_transfer", "hidden", 0); + } + if(!frm.doc.__islocal && (frm.doc.status == "Discharged" || frm.doc.status == "Discharge Scheduled")){ + frm.disable_save(); + frm.set_df_property("btn_transfer", "hidden", 1); + } + }, + btn_transfer: function(frm) { + transfer_patient_dialog(frm); + } +}); + +var discharge_patient = function(frm) { + frappe.call({ + doc: frm.doc, + method: "discharge", + callback: function(data) { + if(!data.exc){ + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: "Process Discharge" + }); +}; + +var admit_patient_dialog = function(frm){ + var dialog = new frappe.ui.Dialog({ + title: 'Admit Patient', + width: 100, + fields: [ + {fieldtype: "Link", label: "Service Unit Type", fieldname: "service_unit_type", options: "Healthcare Service Unit Type"}, + {fieldtype: "Link", label: "Service Unit", fieldname: "service_unit", options: "Healthcare Service Unit", reqd: 1}, + {fieldtype: "Datetime", label: "Admission Datetime", fieldname: "check_in", reqd: 1}, + {fieldtype: "Date", label: "Expected Discharge", fieldname: "expected_discharge"} + ], + primary_action_label: __("Admit"), + primary_action : function(){ + var service_unit = dialog.get_value('service_unit'); + var check_in = dialog.get_value('check_in'); + var expected_discharge = null; + if(dialog.get_value('expected_discharge')){ + expected_discharge = dialog.get_value('expected_discharge'); + } + if(!service_unit && !check_in){ + return; + } + frappe.call({ + doc: frm.doc, + method: 'admit', + args:{ + 'service_unit': service_unit, + 'check_in': check_in, + 'expected_discharge': expected_discharge + }, + callback: function(data) { + if(!data.exc){ + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: "Process Admission" + }); + frm.refresh_fields(); + dialog.hide(); + } + }); + + dialog.fields_dict["service_unit_type"].get_query = function(){ + return { + filters: { + "inpatient_occupancy": 1, + "allow_appointments": 0 + } + }; + }; + dialog.fields_dict["service_unit"].get_query = function(){ + return { + filters: { + "is_group": 0, + "service_unit_type": dialog.get_value("service_unit_type"), + "occupied" : 0 + } + }; + }; + + dialog.show(); +}; + +var transfer_patient_dialog = function(frm){ + var dialog = new frappe.ui.Dialog({ + title: 'Transfer Patient', + width: 100, + fields: [ + {fieldtype: "Link", label: "Leave From", fieldname: "leave_from", options: "Healthcare Service Unit", reqd: 1, read_only:1}, + {fieldtype: "Link", label: "Service Unit Type", fieldname: "service_unit_type", options: "Healthcare Service Unit Type"}, + {fieldtype: "Link", label: "Transfer To", fieldname: "service_unit", options: "Healthcare Service Unit", reqd: 1}, + {fieldtype: "Datetime", label: "Check In", fieldname: "check_in", reqd: 1} + ], + primary_action_label: __("Transfer"), + primary_action : function(){ + var service_unit = null; + var check_in = dialog.get_value('check_in'); + var leave_from = null; + if(dialog.get_value('leave_from')){ + leave_from = dialog.get_value('leave_from'); + } + if(dialog.get_value('service_unit')){ + service_unit = dialog.get_value('service_unit'); + } + if(!check_in){ + return; + } + frappe.call({ + doc: frm.doc, + method: 'transfer', + args:{ + 'service_unit': service_unit, + 'check_in': check_in, + 'leave_from': leave_from + }, + callback: function(data) { + if(!data.exc){ + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: "Process Transfer" + }); + frm.refresh_fields(); + dialog.hide(); + } + }); + + dialog.fields_dict["leave_from"].get_query = function(){ + return { + query : "erpnext.healthcare.doctype.inpatient_record.inpatient_record.get_leave_from", + filters: {docname:frm.doc.name} + }; + }; + dialog.fields_dict["service_unit_type"].get_query = function(){ + return { + filters: { + "inpatient_occupancy": 1, + "allow_appointments": 0 + } + }; + }; + dialog.fields_dict["service_unit"].get_query = function(){ + return { + filters: { + "is_group": 0, + "service_unit_type": dialog.get_value("service_unit_type"), + "occupied" : 0 + } + }; + }; + + dialog.show(); + + var not_left_service_unit = null; + for(let inpatient_occupancy in frm.doc.inpatient_occupancies){ + if(frm.doc.inpatient_occupancies[inpatient_occupancy].left != 1){ + not_left_service_unit = frm.doc.inpatient_occupancies[inpatient_occupancy].service_unit; + } + } + dialog.set_values({ + 'leave_from': not_left_service_unit + }); +}; diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json new file mode 100644 index 00000000000..2f9f1f1cca6 --- /dev/null +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json @@ -0,0 +1,977 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "naming_series:", + "beta": 0, + "creation": "2018-07-11 17:48:51.404139", + "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": "section_break_1", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "naming_series", + "fieldtype": "Select", + "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": "Series", + "length": 0, + "no_copy": 0, + "options": "IP-", + "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": "patient", + "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": "Patient", + "length": 0, + "no_copy": 0, + "options": "Patient", + "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, + "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": 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, + "fetch_from": "patient.sex", + "fieldname": "gender", + "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": "Gender", + "length": 0, + "no_copy": 0, + "options": "Gender", + "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, + "fetch_from": "patient.blood_group", + "fieldname": "blood_group", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Blood Group", + "length": 0, + "no_copy": 0, + "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", + "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": "dob", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Date of birth", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "patient.mobile", + "fieldname": "mobile", + "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": "Mobile", + "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, + "fetch_from": "patient.email", + "fieldname": "email", + "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": "Email", + "length": 0, + "no_copy": 0, + "options": "Email", + "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, + "fetch_from": "patient.phone", + "fieldname": "phone", + "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": "Phone", + "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_8", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged", + "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": "Today", + "fieldname": "scheduled_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Admission Schedule 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": 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": "Today", + "fieldname": "admitted_datetime", + "fieldtype": "Datetime", + "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": "Admitted Datetime", + "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": "expected_discharge", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Expected Discharge", + "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": "discharge_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Discharge 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": 1, + "columns": 0, + "fieldname": "references", + "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": "References", + "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": "cb_admission", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Admission", + "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": "admission_practitioner", + "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": "Healthcare Practitioner", + "length": 0, + "no_copy": 0, + "options": "Healthcare Practitioner", + "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": "admission_encounter", + "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": "Patient Encounter", + "length": 0, + "no_copy": 0, + "options": "Patient Encounter", + "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": "cb_discharge", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Discharge", + "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": "discharge_practitioner", + "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": "Healthcare Practitioner", + "length": 0, + "no_copy": 0, + "options": "Healthcare Practitioner", + "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": "discharge_encounter", + "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": "Patient Encounter", + "length": 0, + "no_copy": 0, + "options": "Patient Encounter", + "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": "sb_inpatient_occupancy", + "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": "Inpatient Occupancy", + "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_occupancies", + "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, + "length": 0, + "no_copy": 0, + "options": "Inpatient Occupancy", + "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": "btn_transfer", + "fieldtype": "Button", + "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", + "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": "eval:doc.status != \"Admission Scheduled\"", + "fieldname": "sb_discharge_note", + "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": "Discharge 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, + "fieldname": "discharge_note", + "fieldtype": "Text Editor", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "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-18 14:10:30.245833", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Record", + "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": "Healthcare Administrator", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "patient", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1, + "track_seen": 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 new file mode 100644 index 00000000000..07cd9e467ed --- /dev/null +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -0,0 +1,142 @@ +# -*- 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 today, now_datetime +from frappe.model.document import Document +from frappe.desk.reportview import get_match_cond + +class InpatientRecord(Document): + def after_insert(self): + frappe.db.set_value("Patient", self.patient, "inpatient_status", "Admission Scheduled") + frappe.db.set_value("Patient", self.patient, "inpatient_record", self.name) + + def validate(self): + self.validate_already_scheduled_or_admitted() + if self.status == "Discharged": + frappe.db.set_value("Patient", self.patient, "inpatient_status", None) + frappe.db.set_value("Patient", self.patient, "inpatient_record", None) + + def validate_already_scheduled_or_admitted(self): + query = """ + select name, status + from `tabInpatient Record` + where (status = 'Admitted' or status = 'Admission Scheduled') + and name != %(name)s and patient = %(patient)s + """ + + ip_record = frappe.db.sql(query,{ + "name": self.name, + "patient": self.patient + }, as_dict = 1) + + if ip_record: + msg = _(("Already {0} Patient {1} with Inpatient Record ").format(ip_record[0].status, self.patient) \ + + """ {0}""".format(ip_record[0].name)) + frappe.throw(msg) + + def admit(self, service_unit, check_in, expected_discharge=None): + admit_patient(self, service_unit, check_in, expected_discharge) + + def discharge(self): + discharge_patient(self) + + def transfer(self, service_unit, check_in, leave_from): + if leave_from: + patient_leave_service_unit(self, check_in, leave_from) + if service_unit: + transfer_patient(self, service_unit, check_in) + +@frappe.whitelist() +def schedule_inpatient(patient, encounter_id, practitioner): + patient_obj = frappe.get_doc('Patient', patient) + inpatient_record = frappe.new_doc('Inpatient Record') + inpatient_record.patient = patient + inpatient_record.patient_name = patient_obj.patient_name + inpatient_record.gender = patient_obj.sex + inpatient_record.blood_group = patient_obj.blood_group + inpatient_record.dob = patient_obj.dob + inpatient_record.mobile = patient_obj.mobile + inpatient_record.email = patient_obj.email + inpatient_record.phone = patient_obj.phone + inpatient_record.status = "Admission Scheduled" + inpatient_record.scheduled_date = today() + inpatient_record.admission_practitioner = practitioner + inpatient_record.admission_encounter = encounter_id + inpatient_record.save(ignore_permissions = True) + +@frappe.whitelist() +def schedule_discharge(patient, encounter_id, practitioner): + 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" + inpatient_record.save(ignore_permissions = True) + frappe.db.set_value("Patient", patient, "inpatient_status", "Discharge Scheduled") + +def discharge_patient(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) + + inpatient_record.discharge_date = today() + inpatient_record.status = "Discharged" + + inpatient_record.save(ignore_permissions = True) + +def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None): + inpatient_record.admitted_datetime = check_in + inpatient_record.status = "Admitted" + inpatient_record.expected_discharge = expected_discharge + + inpatient_record.set('inpatient_occupancies', []) + transfer_patient(inpatient_record, service_unit, check_in) + + frappe.db.set_value("Patient", inpatient_record.patient, "inpatient_status", "Admitted") + frappe.db.set_value("Patient", inpatient_record.patient, "inpatient_record", inpatient_record.name) + +def transfer_patient(inpatient_record, service_unit, check_in): + item_line = inpatient_record.append('inpatient_occupancies', {}) + item_line.service_unit = service_unit + item_line.check_in = check_in + + inpatient_record.save(ignore_permissions = True) + + frappe.db.set_value("Healthcare Service Unit", service_unit, "occupied", True) + +def patient_leave_service_unit(inpatient_record, check_out, leave_from): + if inpatient_record.inpatient_occupancies: + for inpatient_occupancy in inpatient_record.inpatient_occupancies: + 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) + inpatient_record.save(ignore_permissions = True) + +@frappe.whitelist() +def get_leave_from(doctype, txt, searchfield, start, page_len, filters): + docname = filters['docname'] + + query = '''select io.service_unit + from `tabInpatient Occupancy` io, `tabInpatient Record` ir + where io.parent = '{docname}' and io.parentfield = 'inpatient_occupancies' + and io.left!=1 and io.parent = ir.name''' + + return frappe.db.sql(query.format(**{ + "docname": docname, + "searchfield": searchfield, + "mcond": get_match_cond(doctype) + }), { + 'txt': "%%%s%%" % txt, + '_txt': txt.replace("%", ""), + 'start': start, + 'page_len': page_len + }) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record_dashboard.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record_dashboard.py new file mode 100644 index 00000000000..0dc89701a85 --- /dev/null +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record_dashboard.py @@ -0,0 +1,16 @@ +from frappe import _ + +def get_data(): + return { + 'fieldname': 'inpatient_record', + 'transactions': [ + { + 'label': _('Appointments and Encounters'), + 'items': ['Patient Appointment', 'Patient Encounter'] + }, + { + 'label': _('Lab Tests and Vital Signs'), + 'items': ['Lab Test', 'Clinical Procedure', 'Sample Collection', 'Vital Signs'] + } + ] + } diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js new file mode 100644 index 00000000000..1ce9afa96d4 --- /dev/null +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.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: Inpatient Record", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Inpatient Record + () => frappe.tests.make('Inpatient Record', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py new file mode 100644 index 00000000000..b192064f61a --- /dev/null +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +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 + +class TestInpatientRecord(unittest.TestCase): + def test_admit_and_discharge(self): + patient = get_patient() + # Schedule Admission + ip_record = create_inpatient(patient) + ip_record.save(ignore_permissions = True) + self.assertEqual(ip_record.name, frappe.db.get_value("Patient", patient, "inpatient_record")) + self.assertEqual(ip_record.status, frappe.db.get_value("Patient", patient, "inpatient_status")) + + # Admit + 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")) + + # Discharge + discharge_patient(ip_record) + 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`""") + patient = get_patient() + + ip_record = create_inpatient(patient) + ip_record.save(ignore_permissions = True) + ip_record_new = create_inpatient(patient) + self.assertRaises(frappe.ValidationError, ip_record_new.save) + + service_unit = get_healthcare_service_unit() + admit_patient(ip_record, service_unit, now_datetime()) + ip_record_new = create_inpatient(patient) + self.assertRaises(frappe.ValidationError, ip_record_new.save) + frappe.db.sql("""delete from `tabInpatient Record`""") + +def create_inpatient(patient): + patient_obj = frappe.get_doc('Patient', patient) + inpatient_record = frappe.new_doc('Inpatient Record') + inpatient_record.patient = patient + inpatient_record.patient_name = patient_obj.patient_name + inpatient_record.gender = patient_obj.sex + inpatient_record.blood_group = patient_obj.blood_group + inpatient_record.dob = patient_obj.dob + inpatient_record.mobile = patient_obj.mobile + inpatient_record.email = patient_obj.email + inpatient_record.phone = patient_obj.phone + inpatient_record.inpatient = "Scheduled" + inpatient_record.scheduled_date = today() + return inpatient_record + +def get_patient(): + patient = get_random("Patient") + if not patient: + patient = frappe.new_doc("Patient") + patient.patient_name = "Test Patient" + patient.sex = "Male" + patient.save(ignore_permissions=True) + return patient.name + return patient + + +def get_healthcare_service_unit(): + service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1}) + if not service_unit: + service_unit = frappe.new_doc("Healthcare Service Unit") + 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.is_group = 0 + service_unit_parent_name = frappe.db.exists({ + "doctype": "Healthcare Service Unit", + "healthcare_service_unit_name": "All Healthcare Service Units", + "is_group": 1 + }) + if not service_unit_parent_name: + parent_service_unit = frappe.new_doc("Healthcare Service Unit") + parent_service_unit.healthcare_service_unit_name = "All Healthcare Service Units" + parent_service_unit.is_group = 1 + parent_service_unit.save(ignore_permissions = True) + service_unit.parent_healthcare_service_unit = "All Healthcare Service Units" + service_unit.save(ignore_permissions = True) + return service_unit.name + return service_unit + +def get_service_unit_type(): + service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1}) + + if not service_unit_type: + service_unit_type = frappe.new_doc("Healthcare Service Unit Type") + service_unit_type.service_unit_type = "Test Service Unit Type Ip Occupancy" + service_unit_type.inpatient_occupancy = 1 + service_unit_type.save(ignore_permissions = True) + return service_unit_type.name + return service_unit_type diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json index ae66325f6d4..0132c98e10d 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.json +++ b/erpnext/healthcare/doctype/lab_test/lab_test.json @@ -13,6 +13,40 @@ "document_type": "Document", "editable_grid": 0, "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "patient.inpatient_record", + "fieldname": "inpatient_record", + "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 Record", + "length": 0, + "no_copy": 0, + "options": "Inpatient Record", + "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, @@ -86,6 +120,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", "hidden": 0, @@ -1481,7 +1516,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-07-16 12:47:01.425117", + "modified": "2018-07-17 12:47:01.425117", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Test", @@ -1557,4 +1592,4 @@ "title_field": "patient", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json index 10cc11abdc7..b442a194f1f 100644 --- a/erpnext/healthcare/doctype/patient/patient.json +++ b/erpnext/healthcare/doctype/patient/patient.json @@ -16,6 +16,7 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -49,6 +50,73 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "inpatient_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": "Inpatient Status", + "length": 0, + "no_copy": 0, + "options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled", + "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": "inpatient_record", + "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 Record", + "length": 0, + "no_copy": 0, + "options": "Inpatient Record", + "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, @@ -81,6 +149,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 1, "collapsible": 0, @@ -113,6 +182,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -145,6 +215,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -177,6 +248,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -208,6 +280,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -239,6 +312,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -272,6 +346,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -304,6 +379,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -334,6 +410,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -366,6 +443,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -398,6 +476,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -429,6 +508,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -461,6 +541,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -492,6 +573,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -524,6 +606,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -555,6 +638,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -587,6 +671,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -618,6 +703,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -649,6 +735,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -680,6 +767,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -710,6 +798,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -741,6 +830,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -772,6 +862,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -803,6 +894,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -834,6 +926,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -864,6 +957,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -896,6 +990,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -927,6 +1022,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -958,6 +1054,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -989,6 +1086,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1020,6 +1118,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1051,6 +1150,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1081,6 +1181,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1112,6 +1213,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1143,6 +1245,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -1177,6 +1280,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1209,6 +1313,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -1240,6 +1345,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1283,7 +1389,7 @@ "issingle": 0, "istable": 0, "max_attachments": 50, - "modified": "2018-03-13 11:20:31.338415", + "modified": "2018-07-18 13:36:05.308926", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient", @@ -1292,7 +1398,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 0, @@ -1312,7 +1417,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 0, @@ -1332,7 +1436,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 0, diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index f5e59f23fdd..9799018cea4 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -97,6 +97,7 @@ frappe.ui.form.on('Patient Appointment', { },__("Create")); } } + frm.set_df_property("get_procedure_from_encounter", "read_only", frm.doc.__islocal ? 0 : 1); }, check_availability: function(frm) { var { practitioner, appointment_date } = frm.doc; @@ -158,25 +159,25 @@ frappe.ui.form.on('Patient Appointment', { slot_html = slot_html + `+
{{ _("Loading Payment System") }}
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.py b/erpnext/templates/pages/integrations/gocardless_checkout.py index 3c2466ea2f3..2ba7001a615 100644 --- a/erpnext/templates/pages/integrations/gocardless_checkout.py +++ b/erpnext/templates/pages/integrations/gocardless_checkout.py @@ -73,4 +73,4 @@ def check_mandate(data, reference_doctype, reference_docname): except Exception as e: frappe.log_error(e, "GoCardless Payment Error") - return {"redirect_to": '/integrations/payment-failed'} + return {"redirect_to": '/integrations/payment-failed'} \ No newline at end of file diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.html b/erpnext/templates/pages/integrations/gocardless_confirmation.html index 1baf23be26b..6ba154a06c7 100644 --- a/erpnext/templates/pages/integrations/gocardless_confirmation.html +++ b/erpnext/templates/pages/integrations/gocardless_confirmation.html @@ -9,8 +9,8 @@ {% endblock %} {%- block page_content -%} -+
{{ _("Payment Confirmation") }}
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.py b/erpnext/templates/pages/integrations/gocardless_confirmation.py index fc564c3df99..697ed3c80fd 100644 --- a/erpnext/templates/pages/integrations/gocardless_confirmation.py +++ b/erpnext/templates/pages/integrations/gocardless_confirmation.py @@ -36,10 +36,15 @@ def confirm_payment(redirect_flow_id, reference_doctype, reference_docname): "session_token": frappe.session.user }) + confirmation_url = redirect_flow.confirmation_url + gocardless_success_page = frappe.get_hooks('gocardless_success_page') + if gocardless_success_page: + confirmation_url = frappe.get_attr(gocardless_success_page[-1])(reference_doctype, reference_docname) + data = { "mandate": redirect_flow.links.mandate, "customer": redirect_flow.links.customer, - "redirect_to": redirect_flow.confirmation_url, + "redirect_to": confirmation_url, "redirect_message": "Mandate successfully created", "reference_doctype": reference_doctype, "reference_docname": reference_docname @@ -53,7 +58,7 @@ def confirm_payment(redirect_flow_id, reference_doctype, reference_docname): gateway_controller = get_gateway_controller(reference_docname) frappe.get_doc("GoCardless Settings", gateway_controller).create_payment_request(data) - return {"redirect_to": redirect_flow.confirmation_url} + return {"redirect_to": confirmation_url} except Exception as e: frappe.log_error(e, "GoCardless Payment Error") @@ -82,4 +87,4 @@ def create_mandate(data): }).insert(ignore_permissions=True) except Exception: - frappe.log_error(frappe.get_traceback()) + frappe.log_error(frappe.get_traceback()) \ No newline at end of file diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv index cccccc61f41..7a37ee192ff 100644 --- a/erpnext/translations/zh.csv +++ b/erpnext/translations/zh.csv @@ -1,45 +1,45 @@ DocType: Accounting Period,Period Name,期间名称 -DocType: Employee,Salary Mode,薪酬模式 +DocType: Employee,Salary Mode,工资发放方式 DocType: Patient,Divorced,离异 DocType: Support Settings,Post Route Key,邮政路线密钥 DocType: Buying Settings,Allow Item to be added multiple times in a transaction,允许一个交易中存在相同物料 apps/erpnext/erpnext/support/doctype/warranty_claim/warranty_claim.py +33,Cancel Material Visit {0} before cancelling this Warranty Claim,取消此保修要求之前请先取消物料访问{0} -apps/erpnext/erpnext/config/education.py +118,Assessment Reports,评估报告 +apps/erpnext/erpnext/config/education.py +118,Assessment Reports,评估报表 apps/erpnext/erpnext/setup/setup_wizard/data/industry_type.py +19,Consumer Products,消费类产品 DocType: Supplier Scorecard,Notify Supplier,通知供应商 apps/erpnext/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.js +52,Please select Party Type first,请先选择往来单位 DocType: Item,Customer Items,客户物料 DocType: Project,Costing and Billing,成本核算和计费 -apps/erpnext/erpnext/hr/doctype/employee_advance/employee_advance.py +43,Advance account currency should be same as company currency {0},预付帐户货币应与公司货币{0}相同 +apps/erpnext/erpnext/hr/doctype/employee_advance/employee_advance.py +43,Advance account currency should be same as company currency {0},预付科目货币应与公司货币{0}相同 apps/erpnext/erpnext/accounts/doctype/account/account.py +50,Account {0}: Parent account {1} can not be a ledger,科目{0}的上级科目{1}不能是分类账 DocType: Item,Publish Item to hub.erpnext.com,发布项目hub.erpnext.com -apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py +278,Cannot find active Leave Period,找不到有效的休假期 +apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py +278,Cannot find active Leave Period,找不到有效的休假期间 apps/erpnext/erpnext/hr/doctype/employee/employee_dashboard.py +22,Evaluation,评估 DocType: Item,Default Unit of Measure,默认计量单位 DocType: SMS Center,All Sales Partner Contact,所有的销售合作伙伴联系人 -DocType: Department,Leave Approvers,假期审批人 -DocType: Employee,Bio / Cover Letter,生物/求职信 +DocType: Department,Leave Approvers,休假审批人 +DocType: Employee,Bio / Cover Letter,履历/求职信 DocType: Work Order,WO-,WO- DocType: Consultation,Investigations,调查 DocType: Restaurant Order Entry,Click Enter To Add,点击输入要添加 apps/erpnext/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +29,"Missing value for Password, API Key or Shopify URL",缺少密码,API密钥或Shopify网址的值 DocType: Employee,Rented,租 DocType: Purchase Order,PO-,PO- -apps/erpnext/erpnext/public/js/setup_wizard.js +231,All Accounts,所有帐户 -apps/erpnext/erpnext/hr/doctype/employee_transfer/employee_transfer.py +15,Cannot transfer Employee with status Left,无法转移状态为左的员工 +apps/erpnext/erpnext/public/js/setup_wizard.js +231,All Accounts,所有科目 +apps/erpnext/erpnext/hr/doctype/employee_transfer/employee_transfer.py +15,Cannot transfer Employee with status Left,状态为已离职的员工不能进行调动 DocType: Vehicle Service,Mileage,里程 apps/erpnext/erpnext/assets/doctype/asset/asset.js +309,Do you really want to scrap this asset?,难道你真的想放弃这项资产? DocType: Drug Prescription,Update Schedule,更新时间排程 apps/erpnext/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js +44,Select Default Supplier,选择默认供应商 DocType: Exchange Rate Revaluation Account,New Exchange Rate,新汇率 -apps/erpnext/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py +37,Currency is required for Price List {0},价格表{0}需要制定货币 +apps/erpnext/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py +37,Currency is required for Price List {0},价格清单{0}需要制定货币 DocType: Sales Taxes and Charges Template,* Will be calculated in the transaction.,*将被计算在该交易内。 DocType: Purchase Order,Customer Contact,客户联系 DocType: Patient Appointment,Check availability,检查可用性 DocType: Retention Bonus,Bonus Payment Date,奖金支付日期 DocType: Employee,Job Applicant,求职者 -apps/erpnext/erpnext/buying/doctype/supplier/supplier_dashboard.py +6,This is based on transactions against this Supplier. See timeline below for details,这是基于对这种供应商的交易。详情请参阅以下时间表 -DocType: Manufacturing Settings,Overproduction Percentage For Work Order,工作订单的生产率过高百分比 +apps/erpnext/erpnext/buying/doctype/supplier/supplier_dashboard.py +6,This is based on transactions against this Supplier. See timeline below for details,本统计基于该供应商的过往交易。详情请参阅表单下方的时间线记录 +DocType: Manufacturing Settings,Overproduction Percentage For Work Order,最大允许超订单量入库百分比 apps/erpnext/erpnext/setup/setup_wizard/operations/install_fixtures.py +327,Legal,法律 DocType: Shopify Settings,Sales Order Series,销售订单系列 apps/erpnext/erpnext/hr/utils.py +221,"More than one selection for {0} not \ @@ -48,8 +48,8 @@ apps/erpnext/erpnext/accounts/doctype/payment_entry/payment_entry.js +193,Actual DocType: Allowed To Transact With,Allowed To Transact With,允许与 DocType: Bank Guarantee,Customer,客户 DocType: Purchase Receipt Item,Required By,必选 -DocType: Delivery Note,Return Against Delivery Note,射向送货单 -DocType: Asset Category,Finance Book Detail,财务图书细节 +DocType: Delivery Note,Return Against Delivery Note,基于销售出货单退货 +DocType: Asset Category,Finance Book Detail,账簿信息 DocType: Purchase Order,% Billed,% 已记账 apps/erpnext/erpnext/controllers/sales_and_purchase_return.py +41,Exchange Rate must be same as {0} {1} ({2}),汇率必须一致{0} {1}({2}) DocType: Employee Tax Exemption Declaration,HRA Exemption,HRA豁免 @@ -60,45 +60,45 @@ DocType: Employee Tax Exemption Declaration,HRA as per Salary Structure,HRA根 DocType: Account,Heads (or groups) against which Accounting Entries are made and balances are maintained.,会计分录和维护余额操作针对的组(头) apps/erpnext/erpnext/accounts/doctype/gl_entry/gl_entry.py +196,Outstanding for {0} cannot be less than zero ({1}),未付{0}不能小于零( {1} ) DocType: Manufacturing Settings,Default 10 mins,默认为10分钟 -DocType: Leave Type,Leave Type Name,假期类型名称 +DocType: Leave Type,Leave Type Name,休假类型名称 apps/erpnext/erpnext/templates/pages/projects.js +62,Show open,公开显示 apps/erpnext/erpnext/setup/doctype/naming_series/naming_series.py +156,Series Updated Successfully,系列已成功更新 apps/erpnext/erpnext/templates/includes/cart/cart_dropdown.html +6,Checkout,查看 apps/erpnext/erpnext/controllers/accounts_controller.py +742,{0} in row {1},{1}行中的{0} DocType: Asset Finance Book,Depreciation Start Date,折旧开始日期 DocType: Pricing Rule,Apply On,应用于 -DocType: Item Price,Multiple Item prices.,多个品目的价格。 -,Purchase Order Items To Be Received,采购订单项目可收 +DocType: Item Price,Multiple Item prices.,多个物料的价格。 +,Purchase Order Items To Be Received,待收货采购订单项 DocType: SMS Center,All Supplier Contact,所有供应商联系人 DocType: Support Settings,Support Settings,支持设置 apps/erpnext/erpnext/projects/doctype/project/project.py +76,Expected End Date can not be less than Expected Start Date,预计结束日期不能小于预期开始日期 apps/erpnext/erpnext/utilities/transaction_base.py +115,Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ,行#{0}:速率必须与{1}:{2}({3} / {4}) -,Batch Item Expiry Status,批处理项到期状态 +,Batch Item Expiry Status,物料批号到期状态 apps/erpnext/erpnext/setup/setup_wizard/operations/install_fixtures.py +160,Bank Draft,银行汇票 -DocType: Mode of Payment Account,Mode of Payment Account,付款方式账户 +DocType: Mode of Payment Account,Mode of Payment Account,付款方式默认科目 DocType: Consultation,Consultation,会诊 -DocType: Accounts Settings,Show Payment Schedule in Print,在打印中显示付款时间表 +DocType: Accounts Settings,Show Payment Schedule in Print,在打印中显示付款工时单 apps/erpnext/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py +21,Sales and Returns,销售和退货 apps/erpnext/erpnext/stock/doctype/item/item.js +58,Show Variants,显示变体 DocType: Academic Term,Academic Term,学期 DocType: Employee Tax Exemption Sub Category,Employee Tax Exemption Sub Category,员工免税子类别 apps/erpnext/erpnext/projects/doctype/project/project_dashboard.py +14,Material,材料 -apps/erpnext/erpnext/setup/setup_wizard/setup_wizard.py +71,Making website,制作网站 +apps/erpnext/erpnext/setup/setup_wizard/setup_wizard.py +71,Making website,创建网站 apps/erpnext/erpnext/hr/doctype/employee_benefit_claim/employee_benefit_claim.py +58,"Maximum benefit of employee {0} exceeds {1} by the sum {2} of benefit application pro-rata component\ - amount and previous claimed amount",员工{0}的最高福利超过{1},福利应用程序按比例分量\金额和上次索赔金额的总和{2} + amount and previous claimed amount",员工{0}的福利已超过{1},按有效天数折算的可用福利扣减已申报金额的总和{2} DocType: Opening Invoice Creation Tool Item,Quantity,数量 ,Customers Without Any Sales Transactions,没有任何销售交易的客户 apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +585,Accounts table cannot be blank.,账表不能为空。 apps/erpnext/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py +164,Loans (Liabilities),借款(负债) DocType: Staffing Plan Detail,Total Estimated Cost,预计总成本 -DocType: Employee Education,Year of Passing,按年排序 +DocType: Employee Education,Year of Passing,年份 DocType: Item,Country of Origin,原产国 DocType: Soil Texture,Soil Texture Criteria,土壤质地标准 apps/erpnext/erpnext/templates/includes/product_page.js +34,In Stock,库存 -apps/erpnext/erpnext/accounts/doctype/pricing_rule/pricing_rule.js +48,Item Code > Item Group > Brand,商品代码>商品分组>品牌 +apps/erpnext/erpnext/accounts/doctype/pricing_rule/pricing_rule.js +48,Item Code > Item Group > Brand,物料代码>物料组>品牌 apps/erpnext/erpnext/public/js/utils/customer_quick_entry.js +16,Primary Contact Details,主要联系方式 apps/erpnext/erpnext/setup/doctype/email_digest/templates/default.html +46,Open Issues,开放式问题 -DocType: Production Plan Item,Production Plan Item,生产计划项目 +DocType: Production Plan Item,Production Plan Item,生产计划项 apps/erpnext/erpnext/hr/doctype/employee/employee.py +159,User {0} is already assigned to Employee {1},用户{0}已经被分配给员工{1} DocType: Lab Test Groups,Add new line,添加新行 apps/erpnext/erpnext/setup/setup_wizard/data/industry_type.py +31,Health Care,医疗保健 @@ -110,7 +110,7 @@ DocType: Lab Prescription,Lab Prescription,实验室处方 apps/erpnext/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py +26,Service Expense,服务费用 apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +951,Serial Number: {0} is already referenced in Sales Invoice: {1},序号:{0}已在销售发票中引用:{1} DocType: Bank Statement Transaction Invoice Item,Invoice,发票 -DocType: Purchase Invoice Item,Item Weight Details,项目重量细节 +DocType: Purchase Invoice Item,Item Weight Details,物料重量 DocType: Asset Maintenance Log,Periodicity,周期性 apps/erpnext/erpnext/accounts/report/trial_balance/trial_balance.py +21,Fiscal Year {0} is required,会计年度{0}是必需的 DocType: Crop Cycle,The minimum distance between rows of plants for optimum growth,植株之间的最小距离,以获得最佳生长 @@ -121,43 +121,43 @@ apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.py +257,Row {0 apps/erpnext/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +78,Row # {0}:,行#{0}: DocType: Timesheet,Total Costing Amount,总成本计算金额 DocType: Delivery Note,Vehicle No,车辆编号 -apps/erpnext/erpnext/manufacturing/doctype/bom/bom.py +163,Please select Price List,请选择价格表 -DocType: Accounts Settings,Currency Exchange Settings,货币兑换设置 +apps/erpnext/erpnext/manufacturing/doctype/bom/bom.py +163,Please select Price List,请选择价格清单 +DocType: Accounts Settings,Currency Exchange Settings,外币汇率设置 apps/erpnext/erpnext/public/js/hub/hub_factory.js +70,Please check your network connection.,请检查您的网络连接。 apps/erpnext/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py +94,Row #{0}: Payment document is required to complete the trasaction,列#{0}:付款单据才能完成trasaction DocType: Work Order Operation,Work In Progress,在制品 apps/erpnext/erpnext/education/report/absent_student_report/absent_student_report.py +13,Please select date,请选择日期 -DocType: Finance Book,Finance Book,金融书 +DocType: Finance Book,Finance Book,账簿 DocType: Daily Work Summary Group,Holiday List,假期列表 apps/erpnext/erpnext/setup/setup_wizard/operations/install_fixtures.py +115,Accountant,会计 -DocType: Hub Settings,Selling Price List,卖价格表 +DocType: Hub Settings,Selling Price List,销售价格清单 DocType: Patient,Tobacco Current Use,烟草当前使用 -apps/erpnext/erpnext/stock/report/item_price_stock/item_price_stock.py +62,Selling Rate,卖出率 +apps/erpnext/erpnext/stock/report/item_price_stock/item_price_stock.py +62,Selling Rate,销售价 DocType: Cost Center,Stock User,库存用户 DocType: Soil Analysis,(Ca+Mg)/K,(钙+镁)/ K DocType: Company,Phone No,电话号码 DocType: Delivery Trip,Initial Email Notification Sent,初始电子邮件通知已发送 -DocType: Bank Statement Settings,Statement Header Mapping,声明标题映射 +DocType: Bank Statement Settings,Statement Header Mapping,对帐单抬头对照关系 ,Sales Partners Commission,销售合作伙伴佣金 DocType: Soil Texture,Sandy Clay Loam,桑迪粘土壤土 DocType: Purchase Invoice,Rounding Adjustment,舍入调整 apps/erpnext/erpnext/setup/doctype/company/company.py +50,Abbreviation cannot have more than 5 characters,缩写不能超过5个字符 -DocType: Physician Schedule Time Slot,Physician Schedule Time Slot,医师计划时间表 -DocType: Payment Request,Payment Request,付款请求 +DocType: Physician Schedule Time Slot,Physician Schedule Time Slot,医师计划工时单 +DocType: Payment Request,Payment Request,付款申请 DocType: Asset,Value After Depreciation,折旧后 DocType: Student,O+,O + apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py +8,Related,有关 -apps/erpnext/erpnext/hr/doctype/attendance/attendance.py +46,Attendance date can not be less than employee's joining date,考勤日期不得少于员工的加盟日期 +apps/erpnext/erpnext/hr/doctype/attendance/attendance.py +46,Attendance date can not be less than employee's joining date,考勤日期不得早于员工入职日期 DocType: Grading Scale,Grading Scale Name,分级标准名称 -apps/erpnext/erpnext/accounts/doctype/account/account.js +37,This is a root account and cannot be edited.,这是一个root帐户,不能被编辑。 +apps/erpnext/erpnext/accounts/doctype/account/account.js +37,This is a root account and cannot be edited.,这是一个顶层(根)科目,不能被编辑。 DocType: Sales Invoice,Company Address,公司地址 DocType: BOM,Operations,操作 apps/erpnext/erpnext/setup/doctype/authorization_rule/authorization_rule.py +38,Cannot set authorization on basis of Discount for {0},不能为{0}设置折扣授权 DocType: Subscription,Subscription Start Date,订阅开始日期 DocType: Rename Tool,"Attach .csv file with two columns, one for the old name and one for the new name",附加.csv文件有两列,一为旧名称,一个用于新名称 -apps/erpnext/erpnext/accounts/utils.py +74,{0} {1} not in any active Fiscal Year.,{0} {1} 没有在任何活动的财政年度中。 -DocType: Packed Item,Parent Detail docname,家长可采用DocName细节 -apps/erpnext/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +69,"Reference: {0}, Item Code: {1} and Customer: {2}",参考:{0},商品编号:{1}和顾客:{2} +apps/erpnext/erpnext/accounts/utils.py +74,{0} {1} not in any active Fiscal Year.,{0} {1} 不在任一激活的财务年度中。 +DocType: Packed Item,Parent Detail docname,上级物料名 +apps/erpnext/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +69,"Reference: {0}, Item Code: {1} and Customer: {2}",参考:{0},物料代号:{1}和顾客:{2} apps/erpnext/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +356,{0} {1} is not present in the parent company,母公司中不存在{0} {1} apps/erpnext/erpnext/accounts/doctype/subscription/subscription.py +221,Trial Period End Date Cannot be before Trial Period Start Date,试用期结束日期不能在试用期开始日期之前 apps/erpnext/erpnext/utilities/user_progress.py +146,Kg,千克 @@ -169,18 +169,18 @@ DocType: Item Attribute,Increment,增量 apps/erpnext/erpnext/utilities/page/leaderboard/leaderboard.js +74,Timespan,时间跨度 apps/erpnext/erpnext/public/js/stock_analytics.js +58,Select Warehouse...,选择仓库... apps/erpnext/erpnext/setup/setup_wizard/data/industry_type.py +6,Advertising,广告 -apps/erpnext/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py +22,Same Company is entered more than once,同一家公司进入不止一次 +apps/erpnext/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py +22,Same Company is entered more than once,公司代码在另一行已输入过,重复了 DocType: Patient,Married,已婚 apps/erpnext/erpnext/accounts/party.py +41,Not permitted for {0},不允许{0} -apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +598,Get items from,从获取物料 +apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +598,Get items from,从...获取物料 DocType: Price List,Price Not UOM Dependant,价格不依赖于UOM -apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +494,Stock cannot be updated against Delivery Note {0},送货单{0}不能更新库存 +apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +494,Stock cannot be updated against Delivery Note {0},销售出货单{0}不能更新库存 apps/erpnext/erpnext/templates/pages/home.py +25,Product {0},产品{0} -apps/erpnext/erpnext/templates/generators/item_group.html +43,No items listed,没有列出项目 +apps/erpnext/erpnext/templates/generators/item_group.html +43,No items listed,没有物料 DocType: Asset Repair,Error Description,错误说明 DocType: Payment Reconciliation,Reconcile,核消(对帐) apps/erpnext/erpnext/setup/setup_wizard/data/industry_type.py +30,Grocery,杂货 -DocType: Quality Inspection Reading,Reading 1,阅读1 +DocType: Quality Inspection Reading,Reading 1,检验结果1 apps/erpnext/erpnext/setup/setup_wizard/data/industry_type.py +40,Pension Funds,养老基金 DocType: Exchange Rate Revaluation Account,Gain/Loss,收益/损失 DocType: Crop,Perennial,多年生 @@ -189,50 +189,50 @@ apps/erpnext/erpnext/healthcare/doctype/consultation/consultation.js +38,Procedu DocType: Accounts Settings,Use Custom Cash Flow Format,使用自定义现金流量格式 DocType: SMS Center,All Sales Person,所有的销售人员 DocType: Monthly Distribution,**Monthly Distribution** helps you distribute the Budget/Target across months if you have seasonality in your business.,**月度分配**帮助你分配预算/目标跨越几个月,如果你在你的业务有季节性。 -apps/erpnext/erpnext/accounts/page/pos/pos.js +1765,Not items found,未找到项目 -apps/erpnext/erpnext/hr/doctype/salary_slip/salary_slip.py +255,Salary Structure Missing,薪酬结构缺失 -DocType: Lead,Person Name,人姓名 -DocType: Sales Invoice Item,Sales Invoice Item,销售发票品目 +apps/erpnext/erpnext/accounts/page/pos/pos.js +1765,Not items found,未找到物料 +apps/erpnext/erpnext/hr/doctype/salary_slip/salary_slip.py +255,Salary Structure Missing,未分配薪资结构 +DocType: Lead,Person Name,姓名 +DocType: Sales Invoice Item,Sales Invoice Item,销售发票物料 DocType: Account,Credit,贷方 -DocType: POS Profile,Write Off Cost Center,核销成本中心 +DocType: POS Profile,Write Off Cost Center,销帐成本中心 apps/erpnext/erpnext/public/js/setup_wizard.js +117,"e.g. ""Primary School"" or ""University""",如“小学”或“大学” -apps/erpnext/erpnext/config/stock.py +28,Stock Reports,库存报告 +apps/erpnext/erpnext/config/stock.py +28,Stock Reports,库存报表 DocType: Warehouse,Warehouse Detail,仓库详细信息 apps/erpnext/erpnext/education/doctype/academic_term/academic_term.py +33,The Term End Date cannot be later than the Year End Date of the Academic Year to which the term is linked (Academic Year {}). Please correct the dates and try again.,该期限结束日期不能晚于学年年终日期到这个词联系在一起(学年{})。请更正日期,然后再试一次。 apps/erpnext/erpnext/stock/doctype/item/item.py +285,"""Is Fixed Asset"" cannot be unchecked, as Asset record exists against the item",是固定资产不能被反选,因为存在资产记录的项目 DocType: Delivery Trip,Departure Time,出发时间 DocType: Vehicle Service,Brake Oil,刹车油 DocType: Tax Rule,Tax Type,税收类型 -,Completed Work Orders,完成的工作订单 +,Completed Work Orders,完成的工单 DocType: Support Settings,Forum Posts,论坛帖子 apps/erpnext/erpnext/controllers/taxes_and_totals.py +598,Taxable Amount,应税金额 apps/erpnext/erpnext/accounts/doctype/gl_entry/gl_entry.py +160,You are not authorized to add or update entries before {0},你没有权限在{0}前添加或更改分录。 -DocType: Leave Policy,Leave Policy Details,退出政策详情 -DocType: BOM,Item Image (if not slideshow),项目图片(如果没有指定幻灯片) +DocType: Leave Policy,Leave Policy Details,休假政策信息 +DocType: BOM,Item Image (if not slideshow),物料图片(如果没有轮播图片) DocType: Work Order Operation,(Hour Rate / 60) * Actual Operation Time,(小时率/ 60)*实际操作时间 -apps/erpnext/erpnext/accounts/doctype/payment_entry/payment_entry.js +1105,Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry,行#{0}:参考文档类型必须是费用索赔或日记帐分录之一 +apps/erpnext/erpnext/accounts/doctype/payment_entry/payment_entry.js +1105,Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry,行#{0}:参考文档类型必须是费用报销或手工凭证之一 apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.js +1022,Select BOM,选择BOM DocType: SMS Log,SMS Log,短信日志 -apps/erpnext/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py +27,Cost of Delivered Items,交付品目成本 +apps/erpnext/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py +27,Cost of Delivered Items,出货物料成本 apps/erpnext/erpnext/hr/doctype/holiday_list/holiday_list.py +39,The holiday on {0} is not between From Date and To Date,在{0}这个节日之间没有从日期和结束日期 DocType: Student Log,Student Log,学生登录 apps/erpnext/erpnext/config/buying.py +165,Templates of supplier standings.,供应商榜单。 DocType: Lead,Interested,有兴趣 -apps/erpnext/erpnext/accounts/report/general_ledger/general_ledger.py +236,Opening,开盘 +apps/erpnext/erpnext/accounts/report/general_ledger/general_ledger.py +236,Opening,开帐 apps/erpnext/erpnext/stock/doctype/stock_entry/stock_entry.py +33,From {0} to {1},从{0}至 {1} apps/erpnext/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.html +234,Program: ,程序: apps/erpnext/erpnext/setup/setup_wizard/setup_wizard.py +50,Failed to setup taxes,无法设置税收 -DocType: Item,Copy From Item Group,从品目组复制 +DocType: Item,Copy From Item Group,从物料组复制 DocType: Delivery Trip,Delivery Notification,送达通知 -DocType: Journal Entry,Opening Entry,开放报名 +DocType: Journal Entry,Opening Entry,开帐凭证 apps/erpnext/erpnext/accounts/doctype/cheque_print_template/cheque_print_template.js +25,Account Pay Only,账户仅用于支付 DocType: Loan,Repay Over Number of Periods,偿还期的超过数 DocType: Stock Entry,Additional Costs,额外费用 apps/erpnext/erpnext/accounts/doctype/account/account.py +140,Account with existing transaction can not be converted to group.,有交易的科目不能被转换为组。 DocType: Lead,Product Enquiry,产品查询 DocType: Education Settings,Validate Batch for Students in Student Group,验证学生组学生的批次 -apps/erpnext/erpnext/hr/doctype/attendance/attendance.py +38,No leave record found for employee {0} for {1},未找到员工的假期记录{0} {1} -DocType: Company,Unrealized Exchange Gain/Loss Account,未实现的汇兑收益/损失账户 +apps/erpnext/erpnext/hr/doctype/attendance/attendance.py +38,No leave record found for employee {0} for {1},未找到员工的休假记录{0} {1} +DocType: Company,Unrealized Exchange Gain/Loss Account,未实现汇兑损益科目 apps/erpnext/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js +23,Please enter company first,请先输入公司 apps/erpnext/erpnext/accounts/doctype/journal_entry/journal_entry.js +426,Please select Company first,请首先选择公司 DocType: Employee Education,Under Graduate,本科 @@ -241,7 +241,7 @@ apps/erpnext/erpnext/selling/report/sales_person_target_variance_item_group_wise DocType: BOM,Total Cost,总成本 DocType: Soil Analysis,Ca/K,钙/ K DocType: Salary Slip,Employee Loan,员工贷款 -DocType: Fee Schedule,Send Payment Request Email,发送付款请求电子邮件 +DocType: Fee Schedule,Send Payment Request Email,发送付款申请电子邮件 apps/erpnext/erpnext/manufacturing/doctype/bom/bom.py +268,Item {0} does not exist in the system or has expired,物料{0}不存在于系统中或已过期 DocType: Supplier,Leave blank if the Supplier is blocked indefinitely,如果供应商被无限期封锁,请留空 apps/erpnext/erpnext/setup/setup_wizard/data/industry_type.py +44,Real Estate,房地产 @@ -249,7 +249,7 @@ apps/erpnext/erpnext/accounts/report/general_ledger/general_ledger.html +1,State apps/erpnext/erpnext/setup/setup_wizard/data/industry_type.py +41,Pharmaceuticals,制药 DocType: Purchase Invoice Item,Is Fixed Asset,是固定的资产 apps/erpnext/erpnext/stock/doctype/stock_entry/stock_entry.py +317,"Available qty is {0}, you need {1}",可用数量:{0},需要:{1} -DocType: Expense Claim Detail,Claim Amount,报销金额 +DocType: Expense Claim Detail,Claim Amount,申报金额 apps/erpnext/erpnext/manufacturing/doctype/work_order/work_order.py +716,Work Order has been {0},工单已{0} DocType: Budget,Applicable on Purchase Order,适用于采购订单 apps/erpnext/erpnext/accounts/doctype/pos_profile/pos_profile.py +56,Duplicate customer group found in the cutomer group table,在CUTOMER组表中找到重复的客户群 @@ -260,7 +260,7 @@ DocType: Asset Settings,Asset Settings,资产设置 apps/erpnext/erpnext/setup/setup_wizard/operations/install_fixtures.py +71,Consumable,耗材 DocType: Student,B-,B- apps/erpnext/erpnext/hub_node/doctype/hub_settings/hub_settings.py +140,Successfully unregistered.,成功注销。 -DocType: Assessment Result,Grade,年级 +DocType: Assessment Result,Grade,职级 DocType: Restaurant Table,No of Seats,座位数 DocType: Sales Invoice Item,Delivered By Supplier,交付供应商 DocType: Asset Maintenance Task,Asset Maintenance Task,资产维护任务 @@ -275,57 +275,57 @@ apps/erpnext/erpnext/stock/doctype/batch/batch.js +111,Select Target Warehouse, apps/erpnext/erpnext/stock/doctype/batch/batch.js +111,Select Target Warehouse,选择目标仓库 apps/erpnext/erpnext/hr/doctype/employee/employee.js +87,Please enter Preferred Contact Email,请输入首选电子邮件联系 DocType: Journal Entry,Contra Entry,对销分录 -DocType: Journal Entry Account,Credit in Company Currency,信用在公司货币 +DocType: Journal Entry Account,Credit in Company Currency,基于公司本货的信用额度 DocType: Lab Test UOM,Lab Test UOM,实验室测试UOM DocType: Delivery Note,Installation Status,安装状态 DocType: BOM,Quality Inspection Template,质量检验模板 apps/erpnext/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js +135,"Do you want to update attendance?