diff --git a/README.md b/README.md index 0f6a52142bf..15782a2e0c4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

ERP made simple

-[![Build Status](https://travis-ci.com/frappe/erpnext.svg)](https://travis-ci.com/frappe/erpnext) +[![Build Status](https://api.travis-ci.com/frappe/erpnext.svg?branch=develop)](https://travis-ci.com/frappe/erpnext) [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext) [![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index b2a3f83e5fd..a18dbffd9ab 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -43,7 +43,7 @@ { "hidden": 0, "label": "Bank Statement", - "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js index 2235298201f..f795dfa83e6 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js @@ -94,8 +94,7 @@ frappe.ui.form.on('Chart of Accounts Importer', { callback: function(r) { if(r.message===false) { frm.set_value("company", ""); - frappe.throw(__(`Transactions against the company already exist! - Chart Of accounts can be imported for company with no transactions`)); + frappe.throw(__("Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions.")); } else { frm.trigger("refresh"); } diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js index c92b58b5809..d79ad5f528f 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js @@ -42,56 +42,56 @@ frappe.ui.form.on('Pricing Rule', {

- ${__('Notes')} + {{__('Notes')}}

- ${__('How Pricing Rule is applied?')} + {{__('How Pricing Rule is applied?')}}

  1. - ${__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")} + {{__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")}}
  2. - ${__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")} + {{__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")}}
  3. - ${__('Pricing Rules are further filtered based on quantity.')} + {{__('Pricing Rules are further filtered based on quantity.')}}
  4. - ${__('If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.')} + {{__('If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.')}}
  5. - ${__('Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:')} + {{__('Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:')}}
  6. - ${__('If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.')} + {{__('If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.')}}
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index a0b0cbb9956..ef77674372b 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -7,6 +7,7 @@ import frappe import unittest from frappe.utils import today from erpnext.accounts.utils import get_fiscal_year +from erpnext.buying.doctype.supplier.test_supplier import create_supplier test_dependencies = ["Supplier Group"] @@ -103,17 +104,20 @@ class TestTaxWithholdingCategory(unittest.TestCase): def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self): invoices = [] - frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS") - pi = create_purchase_invoice(supplier="Test TDS Supplier2") + doc = create_supplier(supplier_name = "Test TDS Supplier ABC", + tax_withholding_category="Single Threshold TDS") + supplier = doc.name + + pi = create_purchase_invoice(supplier=supplier) pi.submit() invoices.append(pi) # TDS not applied - pi = create_purchase_invoice(supplier="Test TDS Supplier2", do_not_apply_tds=True) + pi = create_purchase_invoice(supplier=supplier, do_not_apply_tds=True) pi.submit() invoices.append(pi) - pi = create_purchase_invoice(supplier="Test TDS Supplier2") + pi = create_purchase_invoice(supplier=supplier) pi.submit() invoices.append(pi) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js index 97035278754..6ae81d74021 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js @@ -156,7 +156,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload { setup_transactions_dom() { const me = this; - me.parent.$main_section.append(`
`) + me.parent.$main_section.append('
'); } create_datatable() { @@ -167,9 +167,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload { }) } catch(err) { - let msg = __(`Your file could not be processed by ERPNext. -
It should be a standard CSV or XLSX file. -
The headers should be in the first row.`) + let msg = __("Your file could not be processed. It should be a standard CSV or XLSX file with headers in the first row."); frappe.throw(msg) } diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 7ad164a8b9b..b2318a2bc62 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -373,8 +373,8 @@ frappe.ui.form.on('Asset', { doctype_field = frappe.scrub(doctype) frm.set_value(doctype_field, ''); frappe.msgprint({ - title: __(`Invalid ${doctype}`), - message: __(`The selected ${doctype} doesn't contains selected Asset Item.`), + title: __('Invalid {0}', [__(doctype)]), + message: __('The selected {0} does not contain the selected Asset Item.', [__(doctype)]), indicator: 'red' }); } @@ -436,7 +436,7 @@ frappe.ui.form.on('Asset Finance Book', { depreciation_start_date: function(frm, cdt, cdn) { const book = locals[cdt][cdn]; if (frm.doc.available_for_use_date && book.depreciation_start_date == frm.doc.available_for_use_date) { - frappe.msgprint(__(`Depreciation Posting Date should not be equal to Available for Use Date.`)); + frappe.msgprint(__("Depreciation Posting Date should not be equal to Available for Use Date.")); book.depreciation_start_date = ""; frm.refresh_field("finance_books"); } diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index a377ec90f8b..f9c8d35518d 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -120,3 +120,20 @@ class TestSupplier(unittest.TestCase): # Rollback address.delete() + +def create_supplier(**args): + args = frappe._dict(args) + + try: + doc = frappe.get_doc({ + "doctype": "Supplier", + "supplier_name": args.supplier_name, + "supplier_group": args.supplier_group or "Services", + "supplier_type": args.supplier_type or "Company", + "tax_withholding_category": args.tax_withholding_category + }).insert() + + return doc + + except frappe.DuplicateEntryError: + return frappe.get_doc("Supplier", args.supplier_name) \ No newline at end of file diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 7504746e078..515239a982f 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -416,26 +416,26 @@ class SellingController(StockController): return for d in self.get('items'): - if self.doctype == "Sales Invoice": - e = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or ''] - f = [d.item_code, d.description, d.sales_order or d.delivery_note] + if self.doctype in ["POS Invoice","Sales Invoice"]: + stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or ''] + non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note] elif self.doctype == "Delivery Note": - e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or ''] - f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice] + stock_items = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or ''] + non_stock_items = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice] elif self.doctype in ["Sales Order", "Quotation"]: - e = [d.item_code, d.description, d.warehouse, ''] - f = [d.item_code, d.description] + stock_items = [d.item_code, d.description, d.warehouse, ''] + non_stock_items = [d.item_code, d.description] if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1: - if e in check_list: + if stock_items in check_list: frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code)) else: - check_list.append(e) + check_list.append(stock_items) else: - if f in chk_dupl_itm: + if non_stock_items in chk_dupl_itm: frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code)) else: - chk_dupl_itm.append(f) + chk_dupl_itm.append(non_stock_items) def validate_target_warehouse(self): items = self.get("items") + (self.get("packed_items") or []) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 4436ab07e56..2f7b361b394 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -229,9 +229,9 @@ class StockController(AccountsController): def check_expense_account(self, item): if not item.get("expense_account"): - frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense Account in the Items table") - .format(item.idx, frappe.bold(item.item_code)), - title=_("Expense Account Missing")) + msg = _("Please set an Expense Account in the Items table") + frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}") + .format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing")) else: is_expense_account = frappe.db.get_value("Account", @@ -247,7 +247,9 @@ class StockController(AccountsController): for d in self.items: if not d.batch_no: continue - serial_nos = [sr.name for sr in frappe.get_all("Serial No", {'batch_no': d.batch_no})] + serial_nos = [sr.name for sr in frappe.get_all("Serial No", + {'batch_no': d.batch_no, 'status': 'Inactive'})] + if serial_nos: frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None) diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js index 99b82148d2e..dc3ae8bf41a 100644 --- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js +++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js @@ -4,7 +4,7 @@ function check_times(frm) { let from_time = Date.parse('01/01/2019 ' + d.from_time); let to_time = Date.parse('01/01/2019 ' + d.to_time); if (from_time > to_time) { - frappe.throw(__(`In row ${i + 1} of Appointment Booking Slots : "To Time" must be later than "From Time"`)); + frappe.throw(__('In row {0} of Appointment Booking Slots: "To Time" must be later than "From Time".', [i + 1])); } }); } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py index 8aa7453bd6b..efbaa71924e 100644 --- a/erpnext/erpnext_integrations/connectors/shopify_connection.py +++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py @@ -149,26 +149,28 @@ def create_sales_invoice(shopify_order, shopify_settings, so, old_order_sync=Fal si.shopify_order_number = shopify_order.get("name") si.set_posting_time = 1 si.posting_date = posting_date + si.due_date = posting_date si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-" si.flags.ignore_mandatory = True set_cost_center(si.items, shopify_settings.cost_center) si.insert(ignore_mandatory=True) si.submit() - make_payament_entry_against_sales_invoice(si, shopify_settings) + make_payament_entry_against_sales_invoice(si, shopify_settings, posting_date) frappe.db.commit() def set_cost_center(items, cost_center): for item in items: item.cost_center = cost_center -def make_payament_entry_against_sales_invoice(doc, shopify_settings): +def make_payament_entry_against_sales_invoice(doc, shopify_settings, posting_date=None): from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry - payemnt_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account) - payemnt_entry.flags.ignore_mandatory = True - payemnt_entry.reference_no = doc.name - payemnt_entry.reference_date = nowdate() - payemnt_entry.insert(ignore_permissions=True) - payemnt_entry.submit() + payment_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account) + payment_entry.flags.ignore_mandatory = True + payment_entry.reference_no = doc.name + payment_entry.posting_date = posting_date or nowdate() + payment_entry.reference_date = posting_date or nowdate() + payment_entry.insert(ignore_permissions=True) + payment_entry.submit() def create_delivery_note(shopify_order, shopify_settings, so): if not cint(shopify_settings.sync_delivery_note): diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json index 6546b08db99..81d60481ce6 100644 --- a/erpnext/healthcare/desk_page/healthcare/healthcare.json +++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json @@ -43,7 +43,7 @@ { "hidden": 0, "label": "Reports", - "links": "[\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Patient Appointment Analytics\",\n\t\t\"doctype\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Lab Test Report\",\n\t\t\"doctype\": \"Lab Test\",\n\t\t\"label\": \"Lab Test Report\"\n\t}\n]" + "links": "[\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Patient Appointment Analytics\",\n\t\t\"doctype\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Lab Test Report\",\n\t\t\"doctype\": \"Lab Test\",\n\t\t\"label\": \"Lab Test Report\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Inpatient Medication Orders\",\n\t\t\"doctype\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Orders\"\n\t}\n]" } ], "category": "Domains", @@ -64,7 +64,7 @@ "idx": 0, "is_standard": 1, "label": "Healthcare", - "modified": "2020-06-25 23:50:56.951698", + "modified": "2020-11-23 23:00:48.764377", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare", diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py index 23e75196ee1..5dac23abd90 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py +++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py @@ -274,4 +274,6 @@ def get_filters(entry): def get_current_healthcare_service_unit(inpatient_record): ip_record = frappe.get_doc('Inpatient Record', inpatient_record) - return ip_record.inpatient_occupancies[-1].service_unit \ No newline at end of file + if ip_record.inpatient_occupancies: + return ip_record.inpatient_occupancies[-1].service_unit + return \ No newline at end of file diff --git a/erpnext/healthcare/report/inpatient_medication_orders/__init__.py b/erpnext/healthcare/report/inpatient_medication_orders/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js new file mode 100644 index 00000000000..a10f83760fa --- /dev/null +++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js @@ -0,0 +1,57 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Inpatient Medication Orders"] = { + "filters": [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + reqd: 1 + }, + { + fieldname: "to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.datetime.now_date(), + reqd: 1 + }, + { + fieldname: "patient", + label: __("Patient"), + fieldtype: "Link", + options: "Patient" + }, + { + fieldname: "service_unit", + label: __("Healthcare Service Unit"), + fieldtype: "Link", + options: "Healthcare Service Unit", + get_query: () => { + var company = frappe.query_report.get_filter_value('company'); + return { + filters: { + 'company': company, + 'is_group': 0 + } + } + } + }, + { + fieldname: "show_completed_orders", + label: __("Show Completed Orders"), + fieldtype: "Check", + default: 1 + } + ] +}; diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json new file mode 100644 index 00000000000..9217fa18919 --- /dev/null +++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json @@ -0,0 +1,36 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2020-11-23 17:25:58.802949", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2020-11-23 19:40:20.227591", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Medication Orders", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Inpatient Medication Order", + "report_name": "Inpatient Medication Orders", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Healthcare Administrator" + }, + { + "role": "Nursing User" + }, + { + "role": "Physician" + } + ] +} \ No newline at end of file diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py new file mode 100644 index 00000000000..b9077301bad --- /dev/null +++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py @@ -0,0 +1,198 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit + +def execute(filters=None): + columns = get_columns() + data = get_data(filters) + chart = get_chart_data(data) + + return columns, data, None, chart + +def get_columns(): + return [ + { + "fieldname": "patient", + "fieldtype": "Link", + "label": "Patient", + "options": "Patient", + "width": 200 + }, + { + "fieldname": "healthcare_service_unit", + "fieldtype": "Link", + "label": "Healthcare Service Unit", + "options": "Healthcare Service Unit", + "width": 150 + }, + { + "fieldname": "drug", + "fieldtype": "Link", + "label": "Drug Code", + "options": "Item", + "width": 150 + }, + { + "fieldname": "drug_name", + "fieldtype": "Data", + "label": "Drug Name", + "width": 150 + }, + { + "fieldname": "dosage", + "fieldtype": "Link", + "label": "Dosage", + "options": "Prescription Dosage", + "width": 80 + }, + { + "fieldname": "dosage_form", + "fieldtype": "Link", + "label": "Dosage Form", + "options": "Dosage Form", + "width": 100 + }, + { + "fieldname": "date", + "fieldtype": "Date", + "label": "Date", + "width": 100 + }, + { + "fieldname": "time", + "fieldtype": "Time", + "label": "Time", + "width": 100 + }, + { + "fieldname": "is_completed", + "fieldtype": "Check", + "label": "Is Order Completed", + "width": 100 + }, + { + "fieldname": "healthcare_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "width": 200 + }, + { + "fieldname": "inpatient_medication_entry", + "fieldtype": "Link", + "label": "Inpatient Medication Entry", + "options": "Inpatient Medication Entry", + "width": 200 + }, + { + "fieldname": "inpatient_record", + "fieldtype": "Link", + "label": "Inpatient Record", + "options": "Inpatient Record", + "width": 200 + } + ] + +def get_data(filters): + conditions, values = get_conditions(filters) + + data = frappe.db.sql(""" + SELECT + parent.patient, parent.inpatient_record, parent.practitioner, + child.drug, child.drug_name, child.dosage, child.dosage_form, + child.date, child.time, child.is_completed, child.name + FROM `tabInpatient Medication Order` parent + INNER JOIN `tabInpatient Medication Order Entry` child + ON child.parent = parent.name + WHERE + parent.docstatus = 1 + {conditions} + ORDER BY date, time + """.format(conditions=conditions), values, as_dict=1) + + data = get_inpatient_details(data, filters.get("service_unit")) + + return data + +def get_conditions(filters): + conditions = "" + values = dict() + + if filters.get("company"): + conditions += " AND parent.company = %(company)s" + values["company"] = filters.get("company") + + if filters.get("from_date") and filters.get("to_date"): + conditions += " AND child.date BETWEEN %(from_date)s and %(to_date)s" + values["from_date"] = filters.get("from_date") + values["to_date"] = filters.get("to_date") + + if filters.get("patient"): + conditions += " AND parent.patient = %(patient)s" + values["patient"] = filters.get("patient") + + if not filters.get("show_completed_orders"): + conditions += " AND child.is_completed = 0" + + return conditions, values + + +def get_inpatient_details(data, service_unit): + service_unit_filtered_data = [] + + for entry in data: + entry["healthcare_service_unit"] = get_current_healthcare_service_unit(entry.inpatient_record) + if entry.is_completed: + entry["inpatient_medication_entry"] = get_inpatient_medication_entry(entry.name) + + if service_unit and entry.healthcare_service_unit and service_unit != entry.healthcare_service_unit: + service_unit_filtered_data.append(entry) + + entry.pop("name", None) + + for entry in service_unit_filtered_data: + data.remove(entry) + + return data + +def get_inpatient_medication_entry(order_entry): + return frappe.db.get_value("Inpatient Medication Entry Detail", {"against_imoe": order_entry}, "parent") + +def get_chart_data(data): + if not data: + return None + + labels = ["Pending", "Completed"] + datasets = [] + + status_wise_data = { + "Pending": 0, + "Completed": 0 + } + + for d in data: + if d.is_completed: + status_wise_data["Completed"] += 1 + else: + status_wise_data["Pending"] += 1 + + datasets.append({ + "name": "Inpatient Medication Order Status", + "values": [status_wise_data.get("Pending"), status_wise_data.get("Completed")] + }) + + chart = { + "data": { + "labels": labels, + "datasets": datasets + }, + "type": "donut", + "height": 300 + } + + chart["fieldtype"] = "Data" + + return chart \ No newline at end of file diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py new file mode 100644 index 00000000000..0d3f45f5000 --- /dev/null +++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py @@ -0,0 +1,128 @@ +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import unittest +import frappe +import datetime +from frappe.utils import getdate, now_datetime +from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy +from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge +from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme +from erpnext.healthcare.report.inpatient_medication_orders.inpatient_medication_orders import execute + +class TestInpatientMedicationOrders(unittest.TestCase): + @classmethod + def setUpClass(self): + frappe.db.sql("delete from `tabInpatient Medication Order` where company='_Test Company'") + frappe.db.sql("delete from `tabInpatient Medication Entry` where company='_Test Company'") + self.patient = create_patient() + self.ip_record = create_records(self.patient) + + def test_inpatient_medication_orders_report(self): + filters = { + 'company': '_Test Company', + 'from_date': getdate(), + 'to_date': getdate(), + 'patient': '_Test IPD Patient', + 'service_unit': 'Test Service Unit Ip Occupancy - _TC' + } + + report = execute(filters) + + expected_data = [ + { + 'patient': '_Test IPD Patient', + 'inpatient_record': self.ip_record.name, + 'practitioner': None, + 'drug': 'Dextromethorphan', + 'drug_name': 'Dextromethorphan', + 'dosage': 1.0, + 'dosage_form': 'Tablet', + 'date': getdate(), + 'time': datetime.timedelta(seconds=32400), + 'is_completed': 0, + 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' + }, + { + 'patient': '_Test IPD Patient', + 'inpatient_record': self.ip_record.name, + 'practitioner': None, + 'drug': 'Dextromethorphan', + 'drug_name': 'Dextromethorphan', + 'dosage': 1.0, + 'dosage_form': 'Tablet', + 'date': getdate(), + 'time': datetime.timedelta(seconds=50400), + 'is_completed': 0, + 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' + }, + { + 'patient': '_Test IPD Patient', + 'inpatient_record': self.ip_record.name, + 'practitioner': None, + 'drug': 'Dextromethorphan', + 'drug_name': 'Dextromethorphan', + 'dosage': 1.0, + 'dosage_form': 'Tablet', + 'date': getdate(), + 'time': datetime.timedelta(seconds=75600), + 'is_completed': 0, + 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' + } + ] + + self.assertEqual(expected_data, report[1]) + + filters = frappe._dict(from_date=getdate(), to_date=getdate(), from_time='', to_time='') + ipme = create_ipme(filters) + ipme.submit() + + filters = { + 'company': '_Test Company', + 'from_date': getdate(), + 'to_date': getdate(), + 'patient': '_Test IPD Patient', + 'service_unit': 'Test Service Unit Ip Occupancy - _TC', + 'show_completed_orders': 0 + } + + report = execute(filters) + self.assertEqual(len(report[1]), 0) + + def tearDown(self): + if frappe.db.get_value('Patient', self.patient, 'inpatient_record'): + # cleanup - Discharge + schedule_discharge(frappe.as_json({'patient': self.patient})) + self.ip_record.reload() + mark_invoiced_inpatient_occupancy(self.ip_record) + + self.ip_record.reload() + discharge_patient(self.ip_record) + + for entry in frappe.get_all('Inpatient Medication Entry'): + doc = frappe.get_doc('Inpatient Medication Entry', entry.name) + doc.cancel() + doc.delete() + + for entry in frappe.get_all('Inpatient Medication Order'): + doc = frappe.get_doc('Inpatient Medication Order', entry.name) + doc.cancel() + doc.delete() + + +def create_records(patient): + frappe.db.sql("""delete from `tabInpatient Record`""") + + # Admit + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save() + ip_record.reload() + service_unit = get_healthcare_service_unit() + admit_patient(ip_record, service_unit, now_datetime()) + + ipmo = create_ipmo(patient) + ipmo.submit() + + return ip_record diff --git a/erpnext/hooks.py b/erpnext/hooks.py index b4c57d7c915..741176f33f4 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -237,6 +237,9 @@ doc_events = { "Website Settings": { "validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products" }, + "Tax Category": { + "validate": "erpnext.regional.india.utils.validate_tax_category" + }, "Sales Invoice": { "on_submit": [ "erpnext.regional.create_transaction_log", diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index 9b2de0e1cbc..d337959d534 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -20,7 +20,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): approvers = [] department_details = {} department_list = [] - employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True) + employee = frappe.get_value("Employee", filters.get("employee"), ["employee_name","department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True) employee_department = filters.get("department") or employee.department if employee_department: @@ -59,11 +59,9 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True) if len(approvers) == 0: - frappe.throw(_("Please set {0} for the Employee or for Department: {1}"). - format( - field_name, frappe.bold(employee_department), - frappe.bold(employee.name) - ), - title=_(field_name + " Missing")) + error_msg = _("Please set {0} for the Employee: {1}").format(field_name, frappe.bold(employee.employee_name)) + if department_list: + error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department)) + frappe.throw(error_msg, title=_(field_name + " Missing")) return set(tuple(approver) for approver in approvers) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index e8ecf015c37..d468f52bc0f 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -332,6 +332,7 @@ "read_only": 1 }, { + "depends_on": "eval:doc.is_secured_loan", "fetch_from": "loan_application.maximum_loan_amount", "fieldname": "maximum_loan_amount", "fieldtype": "Currency", @@ -352,7 +353,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-05 10:04:00.762975", + "modified": "2020-11-24 12:27:23.208240", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index 233862bcfe0..f341e81065f 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -171,10 +171,10 @@ def get_total_pledged_security_value(loan): return security_value @frappe.whitelist() -def get_disbursal_amount(loan): - loan_details = frappe.get_all("Loan", fields = ["loan_amount", "disbursed_amount", "total_payment", - "total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan"], - filters= { "name": loan })[0] +def get_disbursal_amount(loan, on_current_security_price=0): + loan_details = frappe.get_value("Loan", loan, ["loan_amount", "disbursed_amount", "total_payment", + "total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan", + "maximum_loan_amount"], as_dict=1) if loan_details.is_secured_loan and frappe.get_all('Loan Security Shortfall', filters={'loan': loan, 'status': 'Pending'}): @@ -188,9 +188,12 @@ def get_disbursal_amount(loan): - flt(loan_details.total_principal_paid) security_value = 0.0 - if loan_details.is_secured_loan: + if loan_details.is_secured_loan and on_current_security_price: security_value = get_total_pledged_security_value(loan) + if loan_details.is_secured_loan and not on_current_security_price: + security_value = flt(loan_details.maximum_loan_amount) + if not security_value and not loan_details.is_secured_loan: security_value = flt(loan_details.loan_amount) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index b051b3243fd..4e8dd41022b 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -31,6 +31,16 @@ frappe.ui.form.on('Job Card', { } } + frm.set_query("quality_inspection", function() { + return { + query: "erpnext.stock.doctype.quality_inspection.quality_inspection.quality_inspection_query", + filters: { + "item_code": frm.doc.production_item, + "reference_name": frm.doc.name + } + }; + }); + frm.trigger("toggle_operation_number"); if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 575e7190430..5713f697e99 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -20,6 +20,7 @@ "production_item", "item_name", "for_quantity", + "quality_inspection", "wip_warehouse", "column_break_12", "employee", @@ -305,11 +306,19 @@ "label": "Sequence Id", "print_hide": 1, "read_only": 1 + }, + { + "depends_on": "eval:!doc.__islocal;", + "fieldname": "quality_inspection", + "fieldtype": "Link", + "label": "Quality Inspection", + "no_copy": 1, + "options": "Quality Inspection" } ], "is_submittable": 1, "links": [], - "modified": "2020-10-14 12:58:25.327897", + "modified": "2020-11-19 18:26:50.531664", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 4dfa78bf217..d15d81ed93d 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -353,17 +353,19 @@ def get_operation_details(work_order, operation): @frappe.whitelist() def get_operations(doctype, txt, searchfield, start, page_len, filters): - if filters.get("work_order"): - args = {"parent": filters.get("work_order")} - if txt: - args["operation"] = ("like", "%{0}%".format(txt)) + if not filters.get("work_order"): + frappe.msgprint(_("Please select a Work Order first.")) + return [] + args = {"parent": filters.get("work_order")} + if txt: + args["operation"] = ("like", "%{0}%".format(txt)) - return frappe.get_all("Work Order Operation", - filters = args, - fields = ["distinct operation as operation"], - limit_start = start, - limit_page_length = page_len, - order_by="idx asc", as_list=1) + return frappe.get_all("Work Order Operation", + filters = args, + fields = ["distinct operation as operation"], + limit_start = start, + limit_page_length = page_len, + order_by="idx asc", as_list=1) @frappe.whitelist() def make_material_request(source_name, target_doc=None): diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js index 2ac6fa073bf..8cd016461cc 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js @@ -25,11 +25,11 @@ frappe.query_reports["BOM Stock Report"] = { ], "formatter": function(value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (column.id == "Item"){ - if (data["Enough Parts to Build"] > 0){ - value = `${data['Item']}` + if (column.id == "item") { + if (data["enough_parts_to_build"] > 0) { + value = `${data['item']}`; } else { - value = `${data['Item']}` + value = `${data['item']}`; } } return value diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 8c6a9cf8d7c..002ddb2f409 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -49,7 +49,10 @@ frappe.ui.form.on("Task", { }, callback: function (r) { if (r.message.length > 0) { - frappe.msgprint(__(`Cannot convert it to non-group. The following child Tasks exist: ${r.message.join(", ")}.`)); + let message = __('Cannot convert Task to non-group because the following child Tasks exist: {0}.', + [r.message.join(", ")] + ); + frappe.msgprint(message); frm.reload_doc(); } } diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue index 057fe8bc617..16d06018ff0 100644 --- a/erpnext/public/js/hub/pages/Category.vue +++ b/erpnext/public/js/hub/pages/Category.vue @@ -32,7 +32,7 @@ export default { item_id_fieldname: 'name', // Constants - empty_state_message: __(`No items in this category yet.`), + empty_state_message: __('No items in this category yet.'), search_value: '', diff --git a/erpnext/public/js/hub/pages/FeaturedItems.vue b/erpnext/public/js/hub/pages/FeaturedItems.vue index ab9990a3230..63ae7e99bbd 100644 --- a/erpnext/public/js/hub/pages/FeaturedItems.vue +++ b/erpnext/public/js/hub/pages/FeaturedItems.vue @@ -33,10 +33,8 @@ export default { // Constants page_title: __('Your Featured Items'), - empty_state_message: __(`No featured items yet. Got to your - - Published Items - and feature upto 8 items that you want to highlight to your customers.`) + empty_state_message: __('No featured items yet. Got to your {0} and feature up to eight items that you want to highlight to your customers.', + [`${__("Published Items")}`]) }; }, created() { @@ -71,9 +69,9 @@ export default { const item_name = this.items.filter(item => item.hub_item_name === hub_item_name); - alert = frappe.show_alert(__(`${item_name} removed. - Undo`), - grace_period/1000, + alert_message = __('{0} removed. {1}', [item_name, + `${__('Undo')}`]); + alert = frappe.show_alert(alert_message, grace_period / 1000, { 'undo-remove': undo_remove.bind(this) } diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue index 51ade42cbae..93002a7b27a 100644 --- a/erpnext/public/js/hub/pages/Item.vue +++ b/erpnext/public/js/hub/pages/Item.vue @@ -113,12 +113,12 @@ export default { let stats = __('No views yet'); if (this.item.view_count) { - const views_message = __(`${this.item.view_count} Views`); + const views_message = __('{0} Views', [this.item.view_count]); const rating_html = get_rating_html(this.item.average_rating); const rating_count = this.item.no_of_ratings > 0 - ? `${this.item.no_of_ratings} reviews` + ? __('{0} reviews', [this.item.no_of_ratings]) : __('No reviews yet'); stats = [views_message, rating_html, rating_count]; @@ -310,7 +310,7 @@ export default { return this.get_item_details(); }) .then(() => { - frappe.show_alert(__(`${this.item.item_name} Updated`)); + frappe.show_alert(__('{0} Updated', [this.item.item_name])); }); }, @@ -337,7 +337,7 @@ export default { }, unpublish_item() { - frappe.confirm(__(`Unpublish {0}?`, [this.item.item_name]), () => { + frappe.confirm(__('Unpublish {0}?', [this.item.item_name]), () => { frappe .call('erpnext.hub_node.api.unpublish_item', { item_code: this.item.item_code, diff --git a/erpnext/public/js/hub/pages/NotFound.vue b/erpnext/public/js/hub/pages/NotFound.vue index 246d31bc681..8901b97802d 100644 --- a/erpnext/public/js/hub/pages/NotFound.vue +++ b/erpnext/public/js/hub/pages/NotFound.vue @@ -27,7 +27,7 @@ export default { }, // Constants - empty_state_message: __(`Sorry! I could not find what you were looking for.`) + empty_state_message: __('Sorry! We could not find what you were looking for.') }; }, } diff --git a/erpnext/public/js/hub/pages/Publish.vue b/erpnext/public/js/hub/pages/Publish.vue index 735f2b92eca..96fa0aae4e5 100644 --- a/erpnext/public/js/hub/pages/Publish.vue +++ b/erpnext/public/js/hub/pages/Publish.vue @@ -75,14 +75,11 @@ export default { // TODO: multiline translations don't work page_title: __('Publish Items'), search_placeholder: __('Search Items ...'), - empty_state_message: __(`No Items selected yet. Browse and click on items below to publish.`), - valid_items_instruction: __(`Only items with an image and description can be published. Please update them if an item in your inventory does not appear.`), + empty_state_message: __('No Items selected yet. Browse and click on items below to publish.'), + valid_items_instruction: __('Only items with an image and description can be published. Please update them if an item in your inventory does not appear.'), last_sync_message: (hub.settings.last_sync_datetime) - ? __(`Last sync was - - ${comment_when(hub.settings.last_sync_datetime)}. - - See your Published Items.`) + ? __('Last sync was {0}.', [`${comment_when(hub.settings.last_sync_datetime)}`]) + + ` ${__('See your Published Items.')}` : '' }; }, @@ -147,11 +144,9 @@ export default { }, add_last_sync_message() { - this.last_sync_message = __(`Last sync was - - ${comment_when(hub.settings.last_sync_datetime)}. - - See your Published Items.`); + this.last_sync_message = __('Last sync was {0}.', + [`${comment_when(hub.settings.last_sync_datetime)}`] + ) + `${__('See your Published Items')}.`; }, clear_last_sync_message() { diff --git a/erpnext/public/js/hub/pages/SavedItems.vue b/erpnext/public/js/hub/pages/SavedItems.vue index c29675acd30..7007ddcf8e7 100644 --- a/erpnext/public/js/hub/pages/SavedItems.vue +++ b/erpnext/public/js/hub/pages/SavedItems.vue @@ -29,7 +29,7 @@ export default { // Constants page_title: __('Saved Items'), - empty_state_message: __(`You haven't saved any items yet.`) + empty_state_message: __('You have not saved any items yet.') }; }, created() { @@ -64,8 +64,13 @@ export default { const item_name = this.items.filter(item => item.hub_item_name === hub_item_name); - alert = frappe.show_alert(__(`${item_name} removed. - Undo`), + alert = frappe.show_alert(` + + ${__('{0} removed.', [item_name], 'A specific Item has been removed.')} + + ${__('Undo', None, 'Undo removal of item.')} + + `, grace_period/1000, { 'undo-remove': undo_remove.bind(this) diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue index 103284289bb..c10841e9848 100644 --- a/erpnext/public/js/hub/pages/Search.vue +++ b/erpnext/public/js/hub/pages/Search.vue @@ -42,7 +42,10 @@ export default { computed: { page_title() { return this.items.length - ? __(`Results for "${this.search_value}" ${this.category !== 'All'? `in category ${this.category}` : ''}`) + ? __('Results for "{0}" {1}', [ + this.search_value, + this.category !== 'All' ? __('in category {0}', [this.category]) : '' + ]) : __('No Items found.'); } }, diff --git a/erpnext/public/js/hub/pages/Seller.vue b/erpnext/public/js/hub/pages/Seller.vue index e339eaa3e5b..c0903c64c37 100644 --- a/erpnext/public/js/hub/pages/Seller.vue +++ b/erpnext/public/js/hub/pages/Seller.vue @@ -136,7 +136,7 @@ export default { this.init = false; this.profile = data.profile; this.items = data.items; - this.item_container_heading = data.is_featured_item? "Features Items":"Popular Items"; + this.item_container_heading = data.is_featured_item ? __('Featured Items') : __('Popular Items'); this.hub_seller = this.items[0].hub_seller; this.recent_seller_reviews = data.recent_seller_reviews; this.seller_product_view_stats = data.seller_product_view_stats; @@ -147,7 +147,7 @@ export default { this.country = __(profile.country); this.site_name = __(profile.site_name); - this.joined_when = __(`Joined ${comment_when(profile.creation)}`); + this.joined_when = __('Joined {0}', [comment_when(profile.creation)]); this.image = profile.logo; this.sections = [ diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index 5d21190e378..092f83903ea 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -161,7 +161,10 @@ erpnext.setup.slides_settings = [ if(r.message){ exist = r.message; me.get_field("bank_account").set_value(""); - frappe.msgprint(__(`Account ${me.values.bank_account} already exists, enter a different name for your bank account`)); + let message = __('Account {0} already exists. Please enter a different name for your bank account.', + [me.values.bank_account] + ); + frappe.msgprint(message); } } }); diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 3b6a28f52c0..3c156479c58 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -31,12 +31,12 @@ erpnext.setup_auto_gst_taxation = (doctype) => { args: { party_details: JSON.stringify(party_details), doctype: frm.doc.doctype, - company: frm.doc.company, - return_taxes: 1 + company: frm.doc.company }, callback: function(r) { if(r.message) { frm.set_value('taxes_and_charges', r.message.taxes_and_charges); + frm.set_value('place_of_supply', r.message.place_of_supply); } else if (frm.doc.is_internal_supplier || frm.doc.is_internal_customer) { frm.set_value('taxes_and_charges', ''); frm.set_value('taxes', []); diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 5458001e9d1..62487ba2aa0 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -51,6 +51,13 @@ def validate_gstin_for_india(doc, method): frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.") .format(doc.gst_state_number)) +def validate_tax_category(doc, method): + if doc.get('gst_state') and frappe.db.get_value('Tax category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}): + if doc.is_inter_state: + frappe.throw(_("Inter State tax category for GST State {0} already exists").format(doc.gst_state)) + else: + frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state)) + def update_gst_category(doc, method): for link in doc.links: if link.link_doctype in ['Customer', 'Supplier']: @@ -85,8 +92,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): total += digit factor = 2 if factor == 1 else 1 if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: - frappe.throw(_("""Invalid {0}! The check digit validation has failed. - Please ensure you've typed the {0} correctly.""".format(label))) + frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label)) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): @@ -150,7 +156,7 @@ def get_place_of_supply(party_details, doctype): return cstr(address.gst_state_number) + "-" + cstr(address.gst_state) @frappe.whitelist() -def get_regional_address_details(party_details, doctype, company, return_taxes=None): +def get_regional_address_details(party_details, doctype, company): if isinstance(party_details, string_types): party_details = json.loads(party_details) party_details = frappe._dict(party_details) @@ -160,7 +166,7 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N if is_internal_transfer(party_details, doctype): party_details.taxes_and_charges = '' party_details.taxes = '' - return + return party_details if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" @@ -168,26 +174,26 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N get_tax_template_for_sez(party_details, master_doctype, company, 'Customer') get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges') and return_taxes: + if party_details.get('taxes_and_charges'): return party_details if not party_details.company_gstin: - return + return party_details elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier') get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges') and return_taxes: + if party_details.get('taxes_and_charges'): return party_details if not party_details.supplier_gstin: - return + return party_details - if not party_details.place_of_supply: return + if not party_details.place_of_supply: return party_details - if not party_details.company_gstin: return + if not party_details.company_gstin: return party_details if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice", @@ -197,12 +203,11 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2]) if not default_tax: - return + return party_details party_details["taxes_and_charges"] = default_tax party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) - if return_taxes: - return party_details + return party_details def is_internal_transfer(party_details, doctype): if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): @@ -516,7 +521,7 @@ def get_address_details(data, doc, company_address, billing_address): data.transType = 1 data.actualToStateCode = data.toStateCode shipping_address = billing_address - + if doc.gst_category == 'SEZ': data.toStateCode = 99 @@ -755,4 +760,4 @@ def make_regional_gl_entries(gl_entries, doc): }, account_currency, item=tax) ) - return gl_entries \ No newline at end of file + return gl_entries diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 3b62c38b866..be845d9d9d5 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -977,15 +977,20 @@ class Item(WebsiteGenerator): # For "Is Stock Item", following doctypes is important # because reserved_qty, ordered_qty and requested_qty updated from these doctypes if field == "is_stock_item": - linked_doctypes += ["Sales Order Item", "Purchase Order Item", "Material Request Item"] + linked_doctypes += ["Sales Order Item", "Purchase Order Item", "Material Request Item", "Product Bundle"] for doctype in linked_doctypes: + filters={"item_code": self.name, "docstatus": 1} + + if doctype == "Product Bundle": + filters={"new_item_code": self.name} + if doctype in ("Purchase Invoice Item", "Sales Invoice Item",): # If Invoice has Stock impact, only then consider it. if self.stock_ledger_created(): return True - elif frappe.db.get_value(doctype, filters={"item_code": self.name, "docstatus": 1}): + elif frappe.db.get_value(doctype, filters): return True def validate_auto_reorder_enabled_in_stock_settings(self): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 9dee9a95e4f..7e619bd59af 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -621,7 +621,8 @@ def make_purchase_invoice(source_name, target_doc=None): "doctype": "Purchase Invoice", "field_map": { "supplier_warehouse":"supplier_warehouse", - "is_return": "is_return" + "is_return": "is_return", + "bill_date": "bill_date" }, "validation": { "docstatus": ["=", 1], diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index 22f29e05b49..376848afaa4 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -31,17 +31,27 @@ frappe.ui.form.on("Quality Inspection", { // item code based on GRN/DN cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) { - const doctype = (doc.reference_type == "Stock Entry") ? - "Stock Entry Detail" : doc.reference_type + " Item"; + let doctype = doc.reference_type; + + if (doc.reference_type !== "Job Card") { + doctype = (doc.reference_type == "Stock Entry") ? + "Stock Entry Detail" : doc.reference_type + " Item"; + } if (doc.reference_type && doc.reference_name) { + let filters = { + "from": doctype, + "inspection_type": doc.inspection_type + }; + + if (doc.reference_type == doctype) + filters["reference_name"] = doc.reference_name; + else + filters["parent"] = doc.reference_name; + return { query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query", - filters: { - "from": doctype, - "parent": doc.reference_name, - "inspection_type": doc.inspection_type - } + filters: filters }; } }, diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json index dd95075e284..f6d76194d94 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json @@ -73,7 +73,7 @@ "fieldname": "reference_type", "fieldtype": "Select", "label": "Reference Type", - "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry", + "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry\nJob Card", "reqd": 1 }, { @@ -236,7 +236,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-21 13:03:11.938072", + "modified": "2020-11-19 17:06:05.409963", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection", diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 399a63a1860..ae4eb9b9956 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -53,16 +53,28 @@ class QualityInspection(Document): def update_qc_reference(self): quality_inspection = self.name if self.docstatus == 1 else "" - doctype = self.reference_type + ' Item' - if self.reference_type == 'Stock Entry': - doctype = 'Stock Entry Detail' - if self.reference_type and self.reference_name: - frappe.db.sql("""update `tab{child_doc}` t1, `tab{parent_doc}` t2 - set t1.quality_inspection = %s, t2.modified = %s - where t1.parent = %s and t1.item_code = %s and t1.parent = t2.name""" - .format(parent_doc=self.reference_type, child_doc=doctype), - (quality_inspection, self.modified, self.reference_name, self.item_code)) + if self.reference_type == 'Job Card': + if self.reference_name: + frappe.db.sql(""" + UPDATE `tab{doctype}` + SET quality_inspection = %s, modified = %s + WHERE name = %s and production_item = %s + """.format(doctype=self.reference_type), + (quality_inspection, self.modified, self.reference_name, self.item_code)) + + else: + doctype = self.reference_type + ' Item' + if self.reference_type == 'Stock Entry': + doctype = 'Stock Entry Detail' + + if self.reference_type and self.reference_name: + frappe.db.sql(""" + UPDATE `tab{child_doc}` t1, `tab{parent_doc}` t2 + SET t1.quality_inspection = %s, t2.modified = %s + WHERE t1.parent = %s and t1.item_code = %s and t1.parent = t2.name + """.format(parent_doc=self.reference_type, child_doc=doctype), + (quality_inspection, self.modified, self.reference_name, self.item_code)) def set_status_based_on_acceptance_formula(self): for reading in self.readings: @@ -95,27 +107,44 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): mcond = get_match_cond(filters["from"]) cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')" - if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']\ - and filters.get("inspection_type") != "In Process": - cond = """and item_code in (select name from `tabItem` where - inspection_required_before_purchase = 1)""" - elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']\ - and filters.get("inspection_type") != "In Process": - cond = """and item_code in (select name from `tabItem` where - inspection_required_before_delivery = 1)""" - elif filters.get('from') == 'Stock Entry Detail': - cond = """and s_warehouse is null""" + if filters.get("parent"): + if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']\ + and filters.get("inspection_type") != "In Process": + cond = """and item_code in (select name from `tabItem` where + inspection_required_before_purchase = 1)""" + elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']\ + and filters.get("inspection_type") != "In Process": + cond = """and item_code in (select name from `tabItem` where + inspection_required_before_delivery = 1)""" + elif filters.get('from') == 'Stock Entry Detail': + cond = """and s_warehouse is null""" - if filters.get('from') in ['Supplier Quotation Item']: - qi_condition = "" + if filters.get('from') in ['Supplier Quotation Item']: + qi_condition = "" - return frappe.db.sql(""" select item_code from `tab{doc}` - where parent=%(parent)s and docstatus < 2 and item_code like %(txt)s - {qi_condition} {cond} {mcond} - order by item_code limit {start}, {page_len}""".format(doc=filters.get('from'), - parent=filters.get('parent'), cond = cond, mcond = mcond, start = start, - page_len = page_len, qi_condition = qi_condition), - {'parent': filters.get('parent'), 'txt': "%%%s%%" % txt}) + return frappe.db.sql(""" + SELECT item_code + FROM `tab{doc}` + WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s + {qi_condition} {cond} {mcond} + ORDER BY item_code limit {start}, {page_len} + """.format(doc=filters.get('from'), + cond = cond, mcond = mcond, start = start, + page_len = page_len, qi_condition = qi_condition), + {'parent': filters.get('parent'), 'txt': "%%%s%%" % txt}) + + elif filters.get("reference_name"): + return frappe.db.sql(""" + SELECT production_item + FROM `tab{doc}` + WHERE name = %(reference_name)s and docstatus < 2 and production_item like %(txt)s + {qi_condition} {cond} {mcond} + ORDER BY production_item + LIMIT {start}, {page_len} + """.format(doc=filters.get("from"), + cond = cond, mcond = mcond, start = start, + page_len = page_len, qi_condition = qi_condition), + {'reference_name': filters.get('reference_name'), 'txt': "%%%s%%" % txt}) @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 067659f64a1..a1666579d12 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -217,7 +217,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-10-13 10:33:29.147682", + "modified": "2020-11-23 15:26:54.225608", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", @@ -235,5 +235,6 @@ ], "quick_entry": 1, "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file