diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 0be9bd8f870..0ece0d8ee62 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -95,7 +95,7 @@ jobs:
run: cd ~/frappe-bench/ && bench --site test_site execute erpnext.setup.utils.before_tests
- name: cypress pre-requisites
- run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 --no-lockfile
+ run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 @testing-library/cypress@^8 --no-lockfile
- name: Build Assets
diff --git a/erpnext/accounts/custom/address.py b/erpnext/accounts/custom/address.py
index c417a493c69..834227bb586 100644
--- a/erpnext/accounts/custom/address.py
+++ b/erpnext/accounts/custom/address.py
@@ -1,7 +1,7 @@
import frappe
from frappe import _
from frappe.contacts.doctype.address.address import Address
-from frappe.contacts.doctype.address.address import get_address_templates
+from frappe.contacts.doctype.address.address import get_address_templates, get_address_display
class ERPNextAddress(Address):
def validate(self):
@@ -22,6 +22,16 @@ class ERPNextAddress(Address):
frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
title=_("Company Not Linked"))
+ def on_update(self):
+ """
+ After Address is updated, update the related 'Primary Address' on Customer.
+ """
+ address_display = get_address_display(self.as_dict())
+ filters = { "customer_primary_address": self.name }
+ customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
+ for customer_name in customers:
+ frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
+
@frappe.whitelist()
def get_shipping_address(company, address = None):
filters = [
diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.js b/erpnext/accounts/doctype/accounting_period/test_accounting_period.js
deleted file mode 100644
index 71ce5b8d04f..00000000000
--- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Accounting Period", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Accounting Period
- () => frappe.tests.make('Accounting Period', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index a246ae51a46..7d0ecfbafd9 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -19,6 +19,7 @@
"delete_linked_ledger_entries",
"book_asset_depreciation_entry_automatically",
"unlink_advance_payment_on_cancelation_of_order",
+ "enable_common_party_accounting",
"post_change_gl_entries",
"enable_discount_accounting",
"tax_settings_section",
@@ -268,6 +269,12 @@
"fieldname": "enable_discount_accounting",
"fieldtype": "Check",
"label": "Enable Discount Accounting"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_common_party_accounting",
+ "fieldtype": "Check",
+ "label": "Enable Common Party Accounting"
}
],
"icon": "icon-cog",
@@ -275,7 +282,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-08-09 13:08:04.335416",
+ "modified": "2021-08-19 11:17:38.788054",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/bank/test_bank.js b/erpnext/accounts/doctype/bank/test_bank.js
deleted file mode 100644
index 9ec264415a6..00000000000
--- a/erpnext/accounts/doctype/bank/test_bank.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Bank", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Bank
- () => frappe.tests.make('Bank', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/bank_account/test_bank_account.js b/erpnext/accounts/doctype/bank_account/test_bank_account.js
deleted file mode 100644
index c20a7990e8c..00000000000
--- a/erpnext/accounts/doctype/bank_account/test_bank_account.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Bank Account", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Bank Account
- () => frappe.tests.make('Bank Account', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js b/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js
deleted file mode 100644
index f59999845af..00000000000
--- a/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Bank Account Subtype", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Bank Account Subtype
- () => frappe.tests.make('Bank Account Subtype', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.js b/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.js
deleted file mode 100644
index 0c60920cf98..00000000000
--- a/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Bank Guarantee", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Bank Guarantee
- () => frappe.tests.make('Bank Guarantee', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 31cfb2da1da..7ea71fc103a 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -21,6 +21,10 @@ class BankTransaction(StatusUpdater):
self.update_allocations()
self.clear_linked_payment_entries()
self.set_status(update=True)
+
+ def on_cancel(self):
+ self.clear_linked_payment_entries(for_cancel=True)
+ self.set_status(update=True)
def update_allocations(self):
if self.payment_entries:
@@ -41,21 +45,46 @@ class BankTransaction(StatusUpdater):
frappe.db.set_value(self.doctype, self.name, "status", "Reconciled")
self.reload()
-
- def clear_linked_payment_entries(self):
+
+ def clear_linked_payment_entries(self, for_cancel=False):
for payment_entry in self.payment_entries:
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
- self.clear_simple_entry(payment_entry)
+ self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
elif payment_entry.payment_document == "Sales Invoice":
- self.clear_sales_invoice(payment_entry)
+ self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
- def clear_simple_entry(self, payment_entry):
- frappe.db.set_value(payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", self.date)
+ def clear_simple_entry(self, payment_entry, for_cancel=False):
+ if payment_entry.payment_document == "Payment Entry":
+ if frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type") == "Internal Transfer":
+ if len(get_reconciled_bank_transactions(payment_entry)) < 2:
+ return
- def clear_sales_invoice(self, payment_entry):
- frappe.db.set_value("Sales Invoice Payment", dict(parenttype=payment_entry.payment_document,
- parent=payment_entry.payment_entry), "clearance_date", self.date)
+ clearance_date = self.date if not for_cancel else None
+ frappe.db.set_value(
+ payment_entry.payment_document, payment_entry.payment_entry,
+ "clearance_date", clearance_date)
+
+ def clear_sales_invoice(self, payment_entry, for_cancel=False):
+ clearance_date = self.date if not for_cancel else None
+ frappe.db.set_value(
+ "Sales Invoice Payment",
+ dict(
+ parenttype=payment_entry.payment_document,
+ parent=payment_entry.payment_entry
+ ),
+ "clearance_date", clearance_date)
+
+def get_reconciled_bank_transactions(payment_entry):
+ reconciled_bank_transactions = frappe.get_all(
+ 'Bank Transaction Payments',
+ filters = {
+ 'payment_entry': payment_entry.payment_entry
+ },
+ fields = ['parent']
+ )
+
+ return reconciled_bank_transactions
def get_total_allocated_amount(payment_entry):
return frappe.db.sql("""
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js
index bff41d5539b..2585ee9c923 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_list.js
@@ -4,10 +4,12 @@
frappe.listview_settings['Bank Transaction'] = {
add_fields: ["unallocated_amount"],
get_indicator: function(doc) {
- if(flt(doc.unallocated_amount)>0) {
- return [__("Unreconciled"), "orange", "unallocated_amount,>,0"];
+ if(doc.docstatus == 2) {
+ return [__("Cancelled"), "red", "docstatus,=,2"];
} else if(flt(doc.unallocated_amount)<=0) {
return [__("Reconciled"), "green", "unallocated_amount,=,0"];
+ } else if(flt(doc.unallocated_amount)>0) {
+ return [__("Unreconciled"), "orange", "unallocated_amount,>,0"];
}
}
};
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js
deleted file mode 100644
index 305119e1370..00000000000
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Bank Transaction", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Bank Transaction
- () => frappe.tests.make('Bank Transaction', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index ce149f96e6f..439d4891194 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -25,7 +25,8 @@ class TestBankTransaction(unittest.TestCase):
def tearDownClass(cls):
for bt in frappe.get_all("Bank Transaction"):
doc = frappe.get_doc("Bank Transaction", bt.name)
- doc.cancel()
+ if doc.docstatus == 1:
+ doc.cancel()
doc.delete()
# Delete directly in DB to avoid validation errors for countries not allowing deletion
@@ -57,6 +58,12 @@ class TestBankTransaction(unittest.TestCase):
clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
self.assertTrue(clearance_date is not None)
+ bank_transaction.reload()
+ bank_transaction.cancel()
+
+ clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
+ self.assertFalse(clearance_date)
+
# Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
def test_debit_credit_output(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
diff --git a/erpnext/accounts/doctype/cash_flow_mapper/test_cash_flow_mapper.js b/erpnext/accounts/doctype/cash_flow_mapper/test_cash_flow_mapper.js
deleted file mode 100644
index 12ca254c5ae..00000000000
--- a/erpnext/accounts/doctype/cash_flow_mapper/test_cash_flow_mapper.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Cash Flow Mapper", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Cash Flow Mapper
- () => frappe.tests.make('Cash Flow Mapper', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.js b/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.js
deleted file mode 100644
index 1970ca806dc..00000000000
--- a/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Cash Flow Mapping", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Cash Flow Mapping
- () => frappe.tests.make('Cash Flow Mapping', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/cash_flow_mapping_template/test_cash_flow_mapping_template.js b/erpnext/accounts/doctype/cash_flow_mapping_template/test_cash_flow_mapping_template.js
deleted file mode 100644
index 12546ce2e3a..00000000000
--- a/erpnext/accounts/doctype/cash_flow_mapping_template/test_cash_flow_mapping_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Cash Flow Mapping Template", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Cash Flow Mapping Template
- () => frappe.tests.make('Cash Flow Mapping Template', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/cash_flow_mapping_template_details/test_cash_flow_mapping_template_details.js b/erpnext/accounts/doctype/cash_flow_mapping_template_details/test_cash_flow_mapping_template_details.js
deleted file mode 100644
index eecabda751b..00000000000
--- a/erpnext/accounts/doctype/cash_flow_mapping_template_details/test_cash_flow_mapping_template_details.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Cash Flow Mapping Template Details", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Cash Flow Mapping Template Details
- () => frappe.tests.make('Cash Flow Mapping Template Details', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js b/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js
deleted file mode 100644
index a7fcc8d8421..00000000000
--- a/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Cashier Closing", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Cashier Closing
- () => frappe.tests.make('Cashier Closing', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js
deleted file mode 100644
index b075a015d60..00000000000
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Chart of Accounts Importer", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Chart of Accounts Importer
- () => frappe.tests.make('Chart of Accounts Importer', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.js b/erpnext/accounts/doctype/coupon_code/test_coupon_code.js
deleted file mode 100644
index 460fedc97f9..00000000000
--- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Coupon Code", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Coupon Code
- () => frappe.tests.make('Coupon Code', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.js
deleted file mode 100644
index 57c6a7871dc..00000000000
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Exchange Rate Revaluation", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Exchange Rate Revaluation
- () => frappe.tests.make('Exchange Rate Revaluation', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.js b/erpnext/accounts/doctype/finance_book/test_finance_book.js
deleted file mode 100644
index 9fb7d4fcc8e..00000000000
--- a/erpnext/accounts/doctype/finance_book/test_finance_book.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Finance Book", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Finance Book
- () => frappe.tests.make('Finance Book', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.js b/erpnext/accounts/doctype/gl_entry/test_gl_entry.js
deleted file mode 100644
index 2986e5e4e3b..00000000000
--- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: GL Entry", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially('GL Entry', [
- // insert a new GL Entry
- () => frappe.tests.make([
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.js b/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.js
deleted file mode 100644
index 68934993914..00000000000
--- a/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Item Tax Template", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Item Tax Template
- () => frappe.tests.make('Item Tax Template', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js b/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js
deleted file mode 100644
index a916b67522c..00000000000
--- a/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Loyalty Point Entry", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Loyalty Point Entry
- () => frappe.tests.make('Loyalty Point Entry', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js
deleted file mode 100644
index 9321c14e1f0..00000000000
--- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Loyalty Program", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Loyalty Program
- () => frappe.tests.make('Loyalty Program', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js
deleted file mode 100644
index f95d0d82138..00000000000
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Opening Invoice Creation Tool", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Opening Invoice Creation Tool
- () => frappe.tests.make('Opening Invoice Creation Tool', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/accounts/doctype/party_link/__init__.py
similarity index 100%
rename from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
rename to erpnext/accounts/doctype/party_link/__init__.py
diff --git a/erpnext/accounts/doctype/party_link/party_link.js b/erpnext/accounts/doctype/party_link/party_link.js
new file mode 100644
index 00000000000..6da9291d64d
--- /dev/null
+++ b/erpnext/accounts/doctype/party_link/party_link.js
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Party Link', {
+ refresh: function(frm) {
+ frm.set_query('primary_role', () => {
+ return {
+ filters: {
+ name: ['in', ['Customer', 'Supplier']]
+ }
+ };
+ });
+
+ frm.set_query('secondary_role', () => {
+ let party_types = Object.keys(frappe.boot.party_account_types)
+ .filter(p => p != frm.doc.primary_role);
+ return {
+ filters: {
+ name: ['in', party_types]
+ }
+ };
+ });
+ },
+
+ primary_role(frm) {
+ frm.set_value('primary_party', '');
+ frm.set_value('secondary_role', '');
+ },
+
+ secondary_role(frm) {
+ frm.set_value('secondary_party', '');
+ }
+});
diff --git a/erpnext/accounts/doctype/party_link/party_link.json b/erpnext/accounts/doctype/party_link/party_link.json
new file mode 100644
index 00000000000..a1bb15f0d6b
--- /dev/null
+++ b/erpnext/accounts/doctype/party_link/party_link.json
@@ -0,0 +1,102 @@
+{
+ "actions": [],
+ "autoname": "ACC-PT-LNK-.###.",
+ "creation": "2021-08-18 21:06:53.027695",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "primary_role",
+ "secondary_role",
+ "column_break_2",
+ "primary_party",
+ "secondary_party"
+ ],
+ "fields": [
+ {
+ "fieldname": "primary_role",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Primary Role",
+ "options": "DocType",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "primary_role",
+ "fieldname": "secondary_role",
+ "fieldtype": "Link",
+ "label": "Secondary Role",
+ "mandatory_depends_on": "primary_role",
+ "options": "DocType"
+ },
+ {
+ "depends_on": "primary_role",
+ "fieldname": "primary_party",
+ "fieldtype": "Dynamic Link",
+ "label": "Primary Party",
+ "mandatory_depends_on": "primary_role",
+ "options": "primary_role"
+ },
+ {
+ "depends_on": "secondary_role",
+ "fieldname": "secondary_party",
+ "fieldtype": "Dynamic Link",
+ "label": "Secondary Party",
+ "mandatory_depends_on": "secondary_role",
+ "options": "secondary_role"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-08-25 20:08:56.761150",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Party Link",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "primary_party",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py
new file mode 100644
index 00000000000..7d58506ce74
--- /dev/null
+++ b/erpnext/accounts/doctype/party_link/party_link.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+class PartyLink(Document):
+ def validate(self):
+ if self.primary_role not in ['Customer', 'Supplier']:
+ frappe.throw(_("Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."),
+ title=_("Invalid Primary Role"))
+
+ existing_party_link = frappe.get_all('Party Link', {
+ 'primary_party': self.secondary_party
+ }, pluck="primary_role")
+ if existing_party_link:
+ frappe.throw(_('{} {} is already linked with another {}')
+ .format(self.secondary_role, self.secondary_party, existing_party_link[0]))
+
+ existing_party_link = frappe.get_all('Party Link', {
+ 'secondary_party': self.primary_party
+ }, pluck="primary_role")
+ if existing_party_link:
+ frappe.throw(_('{} {} is already linked with another {}')
+ .format(self.primary_role, self.primary_party, existing_party_link[0]))
diff --git a/erpnext/accounts/doctype/party_link/test_party_link.py b/erpnext/accounts/doctype/party_link/test_party_link.py
new file mode 100644
index 00000000000..a3ea3959ba4
--- /dev/null
+++ b/erpnext/accounts/doctype/party_link/test_party_link.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestPartyLink(unittest.TestCase):
+ pass
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index abacee985c6..b259b11c638 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -755,9 +755,11 @@ class PaymentEntry(AccountsController):
if self.payment_type in ('Pay', 'Internal Transfer'):
dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
+ rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_from
elif self.payment_type == 'Receive':
dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
+ rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_to
payment_or_advance_account = self.get_party_account_for_taxes()
@@ -779,14 +781,13 @@ class PaymentEntry(AccountsController):
"cost_center": d.cost_center
}, account_currency, item=d))
- #Intentionally use -1 to get net values in party account
if not d.included_in_paid_amount or self.advance_tax_account:
gl_entries.append(
self.get_gl_dict({
"account": payment_or_advance_account,
"against": against,
- dr_or_cr: -1 * tax_amount,
- dr_or_cr + "_in_account_currency": -1 * base_tax_amount
+ rev_dr_or_cr: tax_amount,
+ rev_dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
"cost_center": self.cost_center,
diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.js b/erpnext/accounts/doctype/payment_order/test_payment_order.js
deleted file mode 100644
index f63fc54521c..00000000000
--- a/erpnext/accounts/doctype/payment_order/test_payment_order.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Payment Order", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Payment Order
- () => frappe.tests.make('Payment Order', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.js b/erpnext/accounts/doctype/payment_request/test_payment_request.js
deleted file mode 100644
index 070b595fc6a..00000000000
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* 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/payment_term/test_payment_term.js b/erpnext/accounts/doctype/payment_term/test_payment_term.js
deleted file mode 100644
index b26e42aa37d..00000000000
--- a/erpnext/accounts/doctype/payment_term/test_payment_term.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Payment Term", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Payment Term
- () => frappe.tests.make('Payment Term', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js b/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js
deleted file mode 100644
index 494a0ed21fb..00000000000
--- a/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Payment Terms Template", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Payment Terms Template
- () => frappe.tests.make('Payment Terms Template', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js
deleted file mode 100644
index 48109b159c6..00000000000
--- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: POS Closing Entry", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new POS Closing Entry
- () => frappe.tests.make('POS Closing Entry', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.js b/erpnext/accounts/doctype/pos_profile/test_pos_profile.js
deleted file mode 100644
index 42e5b7f92f1..00000000000
--- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: POS Profile", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially('POS Profile', [
- // insert a new POS Profile
- () => frappe.tests.make([
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.js b/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.js
deleted file mode 100644
index 5449ab76a38..00000000000
--- a/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: POS Profile User", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new POS Profile User
- () => frappe.tests.make('POS Profile User', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/pos_settings/test_pos_settings.js b/erpnext/accounts/doctype/pos_settings/test_pos_settings.js
deleted file mode 100644
index 639c94ed10d..00000000000
--- a/erpnext/accounts/doctype/pos_settings/test_pos_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: POS Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new POS Settings
- () => frappe.tests.make('POS Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 94abf3b3c06..5467cb0bc5b 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -475,7 +475,20 @@ def apply_pricing_rule_on_transaction(doc):
frappe.msgprint(_("User has not applied rule on the invoice {0}")
.format(doc.name))
else:
- doc.set(field, d.get(pr_field))
+ if not d.coupon_code_based:
+ doc.set(field, d.get(pr_field))
+ elif doc.get('coupon_code'):
+ # coupon code based pricing rule
+ coupon_code_pricing_rule = frappe.db.get_value('Coupon Code', doc.get('coupon_code'), 'pricing_rule')
+ if coupon_code_pricing_rule == d.name:
+ # if selected coupon code is linked with pricing rule
+ doc.set(field, d.get(pr_field))
+ else:
+ # reset discount if not linked
+ doc.set(field, 0)
+ else:
+ # if coupon code based but no coupon code selected
+ doc.set(field, 0)
doc.calculate_taxes_and_totals()
elif d.price_or_product_discount == 'Product':
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index a16795e6287..e2f02f37ee7 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -415,6 +415,8 @@ class PurchaseInvoice(BuyingController):
self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
+ self.process_common_party_accounting()
+
def make_gl_entries(self, gl_entries=None, from_repost=False):
if not gl_entries:
gl_entries = self.get_gl_entries()
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 5023c9c61a3..d8aa32e224b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -247,7 +247,7 @@
"depends_on": "customer",
"fetch_from": "customer.customer_name",
"fieldname": "customer_name",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"hide_days": 1,
"hide_seconds": 1,
"in_global_search": 1,
@@ -692,10 +692,11 @@
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "options": "Barcode",
"hide_days": 1,
"hide_seconds": 1,
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "length": 1,
+ "options": "Barcode"
},
{
"allow_bulk_edit": 1,
@@ -1059,6 +1060,7 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Apply Additional Discount On",
+ "length": 15,
"options": "\nGrand Total\nNet Total",
"print_hide": 1
},
@@ -1145,7 +1147,7 @@
{
"description": "In Words will be visible once you save the Sales Invoice.",
"fieldname": "base_in_words",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"hide_days": 1,
"hide_seconds": 1,
"label": "In Words (Company Currency)",
@@ -1205,7 +1207,7 @@
},
{
"fieldname": "in_words",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"hide_days": 1,
"hide_seconds": 1,
"label": "In Words",
@@ -1558,6 +1560,7 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Print Language",
+ "length": 6,
"print_hide": 1,
"read_only": 1
},
@@ -1645,6 +1648,7 @@
"hide_seconds": 1,
"in_standard_filter": 1,
"label": "Status",
+ "length": 30,
"no_copy": 1,
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1,
@@ -1704,6 +1708,7 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Is Opening Entry",
+ "length": 4,
"oldfieldname": "is_opening",
"oldfieldtype": "Select",
"options": "No\nYes",
@@ -1715,6 +1720,7 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "C-Form Applicable",
+ "length": 4,
"no_copy": 1,
"options": "No\nYes",
"print_hide": 1
@@ -2015,7 +2021,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2021-08-18 16:07:45.122570",
+ "modified": "2021-08-25 14:46:05.279588",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 1cf0df00dbf..fe3ed1670d2 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -253,6 +253,8 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_submit")
+ self.process_common_party_accounting()
+
def validate_pos_return(self):
if self.is_pos and self.is_return:
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index c3d83c7d74c..e06a3bb5b18 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -151,7 +151,7 @@ class TestSalesInvoice(unittest.TestCase):
si1 = create_sales_invoice(rate=1000)
si2 = create_sales_invoice(rate=300)
si3 = create_sales_invoice(qty=-1, rate=300, is_return=1)
-
+
pe = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Bank - _TC")
pe.append('references', {
@@ -1140,6 +1140,18 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(loss_for_si['credit'], loss_for_return_si['debit'])
self.assertEqual(loss_for_si['debit'], loss_for_return_si['credit'])
+ def test_incoming_rate_for_stand_alone_credit_note(self):
+ return_si = create_sales_invoice(is_return=1, update_stock=1, qty=-1, rate=90000, incoming_rate=10,
+ company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', debit_to='Debtors - TCP1',
+ income_account='Sales - TCP1', expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1')
+
+ incoming_rate = frappe.db.get_value('Stock Ledger Entry', {'voucher_no': return_si.name}, 'incoming_rate')
+ debit_amount = frappe.db.get_value('GL Entry',
+ {'voucher_no': return_si.name, 'account': 'Stock In Hand - TCP1'}, 'debit')
+
+ self.assertEqual(debit_amount, 10.0)
+ self.assertEqual(incoming_rate, 10.0)
+
def test_discount_on_net_total(self):
si = frappe.copy_doc(test_records[2])
si.apply_discount_on = "Net Total"
@@ -1816,23 +1828,13 @@ class TestSalesInvoice(unittest.TestCase):
acc_settings.save()
def test_inter_company_transaction(self):
+ from erpnext.selling.doctype.customer.test_customer import create_internal_customer
- if not frappe.db.exists("Customer", "_Test Internal Customer"):
- customer = frappe.get_doc({
- "customer_group": "_Test Customer Group",
- "customer_name": "_Test Internal Customer",
- "customer_type": "Individual",
- "doctype": "Customer",
- "territory": "_Test Territory",
- "is_internal_customer": 1,
- "represents_company": "_Test Company 1"
- })
-
- customer.append("companies", {
- "company": "Wind Power LLC"
- })
-
- customer.insert()
+ create_internal_customer(
+ customer_name="_Test Internal Customer",
+ represents_company="_Test Company 1",
+ allowed_to_interact_with="Wind Power LLC"
+ )
if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
supplier = frappe.get_doc({
@@ -1958,8 +1960,43 @@ class TestSalesInvoice(unittest.TestCase):
frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
+ def test_sle_if_target_warehouse_exists_accidentally(self):
+ """
+ Check if inward entry exists if Target Warehouse accidentally exists
+ but Customer is not an internal customer.
+ """
+ se = make_stock_entry(
+ item_code="138-CMS Shoe",
+ target="Finished Goods - _TC",
+ company = "_Test Company",
+ qty=1,
+ basic_rate=500
+ )
+
+ si = frappe.copy_doc(test_records[0])
+ si.update_stock = 1
+ si.set_warehouse = "Finished Goods - _TC"
+ si.set_target_warehouse = "Stores - _TC"
+ si.get("items")[0].warehouse = "Finished Goods - _TC"
+ si.get("items")[0].target_warehouse = "Stores - _TC"
+ si.insert()
+ si.submit()
+
+ sles = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": si.name},
+ fields=["name", "actual_qty"])
+
+ # check if only one SLE for outward entry is created
+ self.assertEqual(len(sles), 1)
+ self.assertEqual(sles[0].actual_qty, -1)
+
+ # tear down
+ si.cancel()
+ se.cancel()
+
def test_internal_transfer_gl_entry(self):
## Create internal transfer account
+ from erpnext.selling.doctype.customer.test_customer import create_internal_customer
+
account = create_account(account_name="Unrealized Profit",
parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
@@ -2163,6 +2200,50 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
self.assertTrue(schedule.journal_entry)
+ def test_sales_invoice_against_supplier(self):
+ from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import make_customer
+ from erpnext.buying.doctype.supplier.test_supplier import create_supplier
+
+ # create a customer
+ customer = make_customer(customer="_Test Common Supplier")
+ # create a supplier
+ supplier = create_supplier(supplier_name="_Test Common Supplier").name
+
+ # create a party link between customer & supplier
+ # set primary role as supplier
+ party_link = frappe.new_doc("Party Link")
+ party_link.primary_role = "Supplier"
+ party_link.primary_party = supplier
+ party_link.secondary_role = "Customer"
+ party_link.secondary_party = customer
+ party_link.save()
+
+ # enable common party accounting
+ frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1)
+
+ # create a sales invoice
+ si = create_sales_invoice(customer=customer, parent_cost_center="_Test Cost Center - _TC")
+
+ # check outstanding of sales invoice
+ si.reload()
+ self.assertEqual(si.status, 'Paid')
+ self.assertEqual(flt(si.outstanding_amount), 0.0)
+
+ # check creation of journal entry
+ jv = frappe.get_all('Journal Entry Account', {
+ 'account': si.debit_to,
+ 'party_type': 'Customer',
+ 'party': si.customer,
+ 'reference_type': si.doctype,
+ 'reference_name': si.name
+ }, pluck='credit_in_account_currency')
+
+ self.assertTrue(jv)
+ self.assertEqual(jv[0], si.grand_total)
+
+ party_link.delete()
+ frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0)
+
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'
@@ -2375,7 +2456,8 @@ def create_sales_invoice(**args):
"asset": args.asset or None,
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no,
- "conversion_factor": 1
+ "conversion_factor": 1,
+ "incoming_rate": args.incoming_rate or 0
})
if not args.do_not_save:
@@ -2472,29 +2554,6 @@ def get_taxes_and_charges():
"row_id": 1
}]
-def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
- if not frappe.db.exists("Customer", customer_name):
- customer = frappe.get_doc({
- "customer_group": "_Test Customer Group",
- "customer_name": customer_name,
- "customer_type": "Individual",
- "doctype": "Customer",
- "territory": "_Test Territory",
- "is_internal_customer": 1,
- "represents_company": represents_company
- })
-
- customer.append("companies", {
- "company": allowed_to_interact_with
- })
-
- customer.insert()
- customer_name = customer.name
- else:
- customer_name = frappe.db.get_value("Customer", customer_name)
-
- return customer_name
-
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Supplier", supplier_name):
supplier = frappe.get_doc({
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index c77076cb90e..b90f3f09045 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -53,7 +53,6 @@
"column_break_24",
"base_net_rate",
"base_net_amount",
- "incoming_rate",
"drop_ship",
"delivered_by_supplier",
"accounting",
@@ -81,6 +80,7 @@
"target_warehouse",
"quality_inspection",
"batch_no",
+ "incoming_rate",
"col_break5",
"allow_zero_valuation_rate",
"serial_no",
@@ -807,12 +807,12 @@
"read_only": 1
},
{
+ "depends_on": "eval:parent.is_return && parent.update_stock && !parent.return_against",
"fieldname": "incoming_rate",
"fieldtype": "Currency",
- "label": "Incoming Rate",
+ "label": "Incoming Rate (Costing)",
"no_copy": 1,
- "print_hide": 1,
- "read_only": 1
+ "print_hide": 1
},
{
"depends_on": "eval: doc.uom != doc.stock_uom",
@@ -833,7 +833,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-08-12 20:15:47.668399",
+ "modified": "2021-08-19 13:41:53.435827",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/share_transfer/test_share_transfer.js b/erpnext/accounts/doctype/share_transfer/test_share_transfer.js
deleted file mode 100644
index e5530fa0aa6..00000000000
--- a/erpnext/accounts/doctype/share_transfer/test_share_transfer.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Share Transfer", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Share Transfer
- () => frappe.tests.make('Share Transfer', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/share_type/test_share_type.js b/erpnext/accounts/doctype/share_type/test_share_type.js
deleted file mode 100644
index 620afa2ba85..00000000000
--- a/erpnext/accounts/doctype/share_type/test_share_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Share Type", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Share Type
- () => frappe.tests.make('Share Type', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/shareholder/test_shareholder.js b/erpnext/accounts/doctype/shareholder/test_shareholder.js
deleted file mode 100644
index 61c53120ea6..00000000000
--- a/erpnext/accounts/doctype/shareholder/test_shareholder.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shareholder", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Shareholder
- () => frappe.tests.make('Shareholder', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 7c4ff73d908..8bf7b78f585 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -367,21 +367,25 @@ class Subscription(Document):
)
# Discounts
- if self.additional_discount_percentage:
- invoice.additional_discount_percentage = self.additional_discount_percentage
+ if self.is_trialling():
+ invoice.additional_discount_percentage = 100
+ else:
+ if self.additional_discount_percentage:
+ invoice.additional_discount_percentage = self.additional_discount_percentage
- if self.additional_discount_amount:
- invoice.discount_amount = self.additional_discount_amount
+ if self.additional_discount_amount:
+ invoice.discount_amount = self.additional_discount_amount
- if self.additional_discount_percentage or self.additional_discount_amount:
- discount_on = self.apply_additional_discount
- invoice.apply_discount_on = discount_on if discount_on else 'Grand Total'
+ if self.additional_discount_percentage or self.additional_discount_amount:
+ discount_on = self.apply_additional_discount
+ invoice.apply_discount_on = 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()
if self.submit_invoice:
diff --git a/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.js b/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.js
deleted file mode 100644
index 15d3df2a633..00000000000
--- a/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Subscription Invoice", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Subscription Invoice
- () => frappe.tests.make('Subscription Invoice', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
index 771611a7860..878ae098891 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
@@ -21,7 +21,7 @@
"column_break_13",
"billing_interval_count",
"payment_plan_section",
- "payment_plan_id",
+ "product_price_id",
"column_break_16",
"payment_gateway",
"accounting_dimensions_section",
@@ -114,11 +114,6 @@
"fieldtype": "Section Break",
"label": "Payment Plan"
},
- {
- "fieldname": "payment_plan_id",
- "fieldtype": "Data",
- "label": "Payment Plan"
- },
{
"fieldname": "column_break_16",
"fieldtype": "Column Break"
@@ -144,10 +139,15 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
+ },
+ {
+ "fieldname": "product_price_id",
+ "fieldtype": "Data",
+ "label": "Product Price ID"
}
],
"links": [],
- "modified": "2021-08-09 10:53:44.205774",
+ "modified": "2021-08-13 10:53:44.205774",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan",
diff --git a/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.js b/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.js
deleted file mode 100644
index 3ceb9a60507..00000000000
--- a/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Subscription Plan", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Subscription Plan
- () => frappe.tests.make('Subscription Plan', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.js b/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.js
deleted file mode 100644
index 5a751ea99c3..00000000000
--- a/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Subscription Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Subscription Settings
- () => frappe.tests.make('Subscription Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/tax_category/test_tax_category.js b/erpnext/accounts/doctype/tax_category/test_tax_category.js
deleted file mode 100644
index 5142456d769..00000000000
--- a/erpnext/accounts/doctype/tax_category/test_tax_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Tax Category", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Tax Category
- () => frappe.tests.make('Tax Category', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.js b/erpnext/accounts/doctype/tax_rule/test_tax_rule.js
deleted file mode 100644
index 72d177deffe..00000000000
--- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Tax Rule", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Tax Rule
- () => frappe.tests.make('Tax Rule', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 1536a237dec..0cb872c4b81 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -240,14 +240,15 @@ def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details):
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
tds_amount = 0
invoice_filters = {
- 'name': ('in', vouchers),
- 'docstatus': 1
+ 'name': ('in', vouchers),
+ 'docstatus': 1,
+ 'apply_tds': 1
}
field = 'sum(net_total)'
- if not cint(tax_details.consider_party_ledger_amount):
- invoice_filters.update({'apply_tds': 1})
+ if cint(tax_details.consider_party_ledger_amount):
+ invoice_filters.pop('apply_tds', None)
field = 'sum(grand_total)'
supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.js b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.js
deleted file mode 100644
index eab98d43892..00000000000
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Tax Withholding Category", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Tax Withholding Category
- () => frappe.tests.make('Tax Withholding Category', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
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 1c687e5cb15..0f921db678d 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
@@ -145,6 +145,36 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in invoices:
d.cancel()
+ def test_tds_calculation_on_net_total(self):
+ frappe.db.set_value("Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS")
+ invoices = []
+
+ pi = create_purchase_invoice(supplier = "Test TDS Supplier4", rate = 20000, do_not_save=True)
+ pi.append('taxes', {
+ "category": "Total",
+ "charge_type": "Actual",
+ "account_head": '_Test Account VAT - _TC',
+ "cost_center": 'Main - _TC',
+ "tax_amount": 1000,
+ "description": "Test",
+ "add_deduct_tax": "Add"
+
+ })
+ pi.save()
+ pi.submit()
+ invoices.append(pi)
+
+ # Second Invoice will apply TDS checked
+ pi1 = create_purchase_invoice(supplier = "Test TDS Supplier4", rate = 20000)
+ pi1.submit()
+ invoices.append(pi1)
+
+ self.assertEqual(pi1.taxes[0].tax_amount, 4000)
+
+ #delete invoices to avoid clashing
+ for d in invoices:
+ d.cancel()
+
def cancel_invoices():
purchase_invoices = frappe.get_all("Purchase Invoice", {
'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']],
@@ -220,7 +250,7 @@ def create_sales_invoice(**args):
def create_records():
# create a new suppliers
- for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']:
+ for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', 'Test TDS Supplier4']:
if frappe.db.exists('Supplier', name):
continue
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 5d8d49d6a65..3723c8e0d23 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -78,13 +78,10 @@ def validate_filters(filters, account_details):
def validate_party(filters):
party_type, party = filters.get("party_type"), filters.get("party")
- if party:
- if not party_type:
- frappe.throw(_("To filter based on Party, select Party Type first"))
- else:
- for d in party:
- if not frappe.db.exists(party_type, d):
- frappe.throw(_("Invalid {0}: {1}").format(party_type, d))
+ if party and party_type:
+ for d in party:
+ if not frappe.db.exists(party_type, d):
+ frappe.throw(_("Invalid {0}: {1}").format(party_type, d))
def set_account_currency(filters):
if filters.get("account") or (filters.get('party') and len(filters.party) == 1):
diff --git a/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.js b/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.js
deleted file mode 100644
index f70dcd2f322..00000000000
--- a/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Agriculture Analysis Criteria", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Agriculture Analysis Criteria
- () => frappe.tests.make('Agriculture Analysis Criteria', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.js b/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.js
deleted file mode 100644
index a012c4b1ade..00000000000
--- a/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Agriculture Task", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Agriculture Task
- () => frappe.tests.make('Agriculture Task', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.js b/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.js
deleted file mode 100644
index 786c0471a47..00000000000
--- a/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Plant Analysis", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Plant Analysis
- () => frappe.tests.make('Plant Analysis', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.js b/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.js
deleted file mode 100644
index 29128eba27d..00000000000
--- a/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Soil Analysis", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Soil Analysis
- () => frappe.tests.make('Soil Analysis', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/agriculture/doctype/weather/test_weather.js b/erpnext/agriculture/doctype/weather/test_weather.js
deleted file mode 100644
index b5009a4ccd3..00000000000
--- a/erpnext/agriculture/doctype/weather/test_weather.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Weather", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Weather
- () => frappe.tests.make('Weather', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 8fdbbf95d47..9a61b79ed35 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -59,7 +59,7 @@ def make_depreciation_entry(asset_name, date=None):
"credit_in_account_currency": d.depreciation_amount,
"reference_type": "Asset",
"reference_name": asset.name,
- "cost_center": ""
+ "cost_center": depreciation_cost_center
}
debit_entry = {
diff --git a/erpnext/assets/doctype/asset/test_asset.js b/erpnext/assets/doctype/asset/test_asset.js
deleted file mode 100644
index 6119e382174..00000000000
--- a/erpnext/assets/doctype/asset/test_asset.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Asset
- () => frappe.tests.make('Asset', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.js b/erpnext/assets/doctype/asset_category/test_asset_category.js
deleted file mode 100644
index 7e343b7519e..00000000000
--- a/erpnext/assets/doctype/asset_category/test_asset_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Category", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Asset Category
- () => frappe.tests.make('Asset Category', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js
deleted file mode 100644
index f9b38a1020e..00000000000
--- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Maintenance", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Asset Maintenance
- () => frappe.tests.make('Asset Maintenance', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js b/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js
deleted file mode 100644
index 4e80184ea7b..00000000000
--- a/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Maintenance Log", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Asset Maintenance Log
- () => frappe.tests.make('Asset Maintenance Log', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js b/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js
deleted file mode 100644
index 41bf69623ec..00000000000
--- a/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Maintenance Team", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Asset Maintenance Team
- () => frappe.tests.make('Asset Maintenance Team', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.js b/erpnext/assets/doctype/asset_movement/test_asset_movement.js
deleted file mode 100644
index b9515763c42..00000000000
--- a/erpnext/assets/doctype/asset_movement/test_asset_movement.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Movement", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Asset Movement
- () => frappe.tests.make('Asset Movement', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.js b/erpnext/assets/doctype/asset_repair/test_asset_repair.js
deleted file mode 100644
index 7424ffe2b80..00000000000
--- a/erpnext/assets/doctype/asset_repair/test_asset_repair.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Repair", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Asset Repair
- () => frappe.tests.make('Asset Repair', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.js
deleted file mode 100644
index 32831c61d2e..00000000000
--- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Value Adjustment", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Asset Value Adjustment
- () => frappe.tests.make('Asset Value Adjustment', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/location/test_location.js b/erpnext/assets/doctype/location/test_location.js
deleted file mode 100644
index 3c06b63e820..00000000000
--- a/erpnext/assets/doctype/location/test_location.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Location", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Location
- () => frappe.tests.make('Location', [
- // values to be set
- { location_name: 'Basil Farm' }
- ]),
- () => {
- assert.equal(cur_frm.doc.name, 'Basil Farm');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js b/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js
deleted file mode 100644
index d942e2a1566..00000000000
--- a/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Maintenance Team Member", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Maintenance Team Member
- () => frappe.tests.make('Maintenance Team Member', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 9f82af9b37d..f4af8932b6d 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -14,7 +14,7 @@ from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_a
from erpnext.utilities.transaction_base import TransactionBase
from erpnext.buying.utils import update_last_purchase_rate
from erpnext.controllers.sales_and_purchase_return import validate_return
-from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
+from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled, get_party_account
from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction,
apply_pricing_rule_for_free_items, get_applied_pricing_rules)
from erpnext.exceptions import InvalidCurrency
@@ -1363,6 +1363,67 @@ class AccountsController(TransactionBase):
return False
+ def process_common_party_accounting(self):
+ is_invoice = self.doctype in ['Sales Invoice', 'Purchase Invoice']
+ if not is_invoice:
+ return
+
+ if frappe.db.get_single_value('Accounts Settings', 'enable_common_party_accounting'):
+ party_link = self.get_common_party_link()
+ if party_link and self.outstanding_amount:
+ self.create_advance_and_reconcile(party_link)
+
+ def get_common_party_link(self):
+ party_type, party = self.get_party()
+ return frappe.db.get_value(
+ doctype='Party Link',
+ filters={'secondary_role': party_type, 'secondary_party': party},
+ fieldname=['primary_role', 'primary_party'],
+ as_dict=True
+ )
+
+ def create_advance_and_reconcile(self, party_link):
+ secondary_party_type, secondary_party = self.get_party()
+ primary_party_type, primary_party = party_link.primary_role, party_link.primary_party
+
+ primary_account = get_party_account(primary_party_type, primary_party, self.company)
+ secondary_account = get_party_account(secondary_party_type, secondary_party, self.company)
+
+ jv = frappe.new_doc('Journal Entry')
+ jv.voucher_type = 'Journal Entry'
+ jv.posting_date = self.posting_date
+ jv.company = self.company
+ jv.remark = 'Adjustment for {} {}'.format(self.doctype, self.name)
+
+ reconcilation_entry = frappe._dict()
+ advance_entry = frappe._dict()
+
+ reconcilation_entry.account = secondary_account
+ reconcilation_entry.party_type = secondary_party_type
+ reconcilation_entry.party = secondary_party
+ reconcilation_entry.reference_type = self.doctype
+ reconcilation_entry.reference_name = self.name
+ reconcilation_entry.cost_center = self.cost_center
+
+ advance_entry.account = primary_account
+ advance_entry.party_type = primary_party_type
+ advance_entry.party = primary_party
+ advance_entry.cost_center = self.cost_center
+ advance_entry.is_advance = 'Yes'
+
+ if self.doctype == 'Sales Invoice':
+ reconcilation_entry.credit_in_account_currency = self.outstanding_amount
+ advance_entry.debit_in_account_currency = self.outstanding_amount
+ else:
+ advance_entry.credit_in_account_currency = self.outstanding_amount
+ reconcilation_entry.debit_in_account_currency = self.outstanding_amount
+
+ jv.append('accounts', reconcilation_entry)
+ jv.append('accounts', advance_entry)
+
+ jv.save()
+ jv.submit()
+
@frappe.whitelist()
def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py
index 1898222916b..f43c80416f3 100644
--- a/erpnext/controllers/employee_boarding_controller.py
+++ b/erpnext/controllers/employee_boarding_controller.py
@@ -5,7 +5,9 @@ import frappe
from frappe import _
from frappe.desk.form import assign_to
from frappe.model.document import Document
-from frappe.utils import flt, unique
+from frappe.utils import flt, unique, add_days
+from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
+from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
class EmployeeBoardingController(Document):
'''
@@ -41,10 +43,14 @@ class EmployeeBoardingController(Document):
def create_task_and_notify_user(self):
# create the task for the given project and assign to the concerned person
+ holiday_list = self.get_holiday_list()
+
for activity in self.activities:
if activity.task:
continue
+ dates = self.get_task_dates(activity, holiday_list)
+
task = frappe.get_doc({
'doctype': 'Task',
'project': self.project,
@@ -52,7 +58,9 @@ class EmployeeBoardingController(Document):
'description': activity.description,
'department': self.department,
'company': self.company,
- 'task_weight': activity.task_weight
+ 'task_weight': activity.task_weight,
+ 'exp_start_date': dates[0],
+ 'exp_end_date': dates[1]
}).insert(ignore_permissions=True)
activity.db_set('task', task.name)
@@ -79,6 +87,36 @@ class EmployeeBoardingController(Document):
if users:
self.assign_task_to_users(task, users)
+ def get_holiday_list(self):
+ if self.doctype == 'Employee Separation':
+ return get_holiday_list_for_employee(self.employee)
+ else:
+ if self.employee:
+ return get_holiday_list_for_employee(self.employee)
+ else:
+ if not self.holiday_list:
+ frappe.throw(_('Please set the Holiday List.'), frappe.MandatoryError)
+ else:
+ return self.holiday_list
+
+ def get_task_dates(self, activity, holiday_list):
+ start_date = end_date = None
+
+ if activity.begin_on:
+ start_date = add_days(self.boarding_begins_on, activity.begin_on)
+ start_date = self.update_if_holiday(start_date, holiday_list)
+
+ if activity.duration:
+ end_date = add_days(self.boarding_begins_on, activity.begin_on + activity.duration)
+ end_date = self.update_if_holiday(end_date, holiday_list)
+
+ return [start_date, end_date]
+
+ def update_if_holiday(self, date, holiday_list):
+ while is_holiday(holiday_list, date):
+ date = add_days(date, 1)
+ return date
+
def assign_task_to_users(self, task, users):
for user in users:
args = {
@@ -103,7 +141,8 @@ class EmployeeBoardingController(Document):
@frappe.whitelist()
def get_onboarding_details(parent, parenttype):
return frappe.get_all('Employee Boarding Activity',
- fields=['activity_name', 'role', 'user', 'required_for_employee_creation', 'description', 'task_weight'],
+ fields=['activity_name', 'role', 'user', 'required_for_employee_creation',
+ 'description', 'task_weight', 'begin_on', 'duration'],
filters={'parent': parent, 'parenttype': parenttype},
order_by= 'idx')
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 5ee1f2f7fb5..01486fcd65d 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -394,19 +394,6 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None
if not return_against:
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
- if not return_against and voucher_type == 'Sales Invoice' and sle:
- return get_incoming_rate({
- "item_code": sle.item_code,
- "warehouse": sle.warehouse,
- "posting_date": sle.get('posting_date'),
- "posting_time": sle.get('posting_time'),
- "qty": sle.actual_qty,
- "serial_no": sle.get('serial_no'),
- "company": sle.company,
- "voucher_type": sle.voucher_type,
- "voucher_no": sle.voucher_no
- }, raise_error_if_no_rate=False)
-
return_against_item_field = get_return_against_item_fields(voucher_type)
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
@@ -417,7 +404,24 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None
else:
select_field = "abs(stock_value_difference / actual_qty)"
- return flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+ rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+ if not (rate and return_against) and voucher_type in ['Sales Invoice', 'Delivery Note']:
+ rate = frappe.db.get_value(f'{voucher_type} Item', voucher_detail_no, 'incoming_rate')
+
+ if not rate and sle:
+ rate = get_incoming_rate({
+ "item_code": sle.item_code,
+ "warehouse": sle.warehouse,
+ "posting_date": sle.get('posting_date'),
+ "posting_time": sle.get('posting_time'),
+ "qty": sle.actual_qty,
+ "serial_no": sle.get('serial_no'),
+ "company": sle.company,
+ "voucher_type": sle.voucher_type,
+ "voucher_no": sle.voucher_no
+ }, raise_error_if_no_rate=False)
+
+ return rate
def get_return_against_item_fields(voucher_type):
return_against_item_fields = {
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index fc2cc97e0a5..844c40c8a64 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -362,7 +362,7 @@ class SellingController(StockController):
sales_order.update_reserved_qty(so_item_rows)
def set_incoming_rate(self):
- if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"):
+ if self.doctype not in ("Delivery Note", "Sales Invoice"):
return
items = self.get("items") + (self.get("packed_items") or [])
@@ -371,18 +371,19 @@ class SellingController(StockController):
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get('stock_qty') or d.get('actual_qty'))
- d.incoming_rate = get_incoming_rate({
- "item_code": d.item_code,
- "warehouse": d.warehouse,
- "posting_date": self.get('posting_date') or self.get('transaction_date'),
- "posting_time": self.get('posting_time') or nowtime(),
- "qty": qty if cint(self.get("is_return")) else (-1 * qty),
- "serial_no": d.get('serial_no'),
- "company": self.company,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "allow_zero_valuation": d.get("allow_zero_valuation")
- }, raise_error_if_no_rate=False)
+ if not d.incoming_rate:
+ d.incoming_rate = get_incoming_rate({
+ "item_code": d.item_code,
+ "warehouse": d.warehouse,
+ "posting_date": self.get('posting_date') or self.get('transaction_date'),
+ "posting_time": self.get('posting_time') or nowtime(),
+ "qty": qty if cint(self.get("is_return")) else (-1 * qty),
+ "serial_no": d.get('serial_no'),
+ "company": self.company,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "allow_zero_valuation": d.get("allow_zero_valuation")
+ }, raise_error_if_no_rate=False)
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
@@ -422,7 +423,7 @@ class SellingController(StockController):
or (cint(self.is_return) and self.docstatus==2)):
sl_entries.append(self.get_sle_for_source_warehouse(d))
- if d.target_warehouse:
+ if d.target_warehouse and self.get("is_internal_customer"):
sl_entries.append(self.get_sle_for_target_warehouse(d))
if d.warehouse and ((not cint(self.is_return) and self.docstatus==2)
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index b1f89b08d79..7b24e50b143 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -86,7 +86,8 @@ status_map = {
],
"Bank Transaction": [
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],
- ["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"]
+ ["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"],
+ ["Cancelled", "eval:self.docstatus == 2"]
],
"POS Opening Entry": [
["Draft", None],
diff --git a/erpnext/crm/doctype/contract/test_contract.js b/erpnext/crm/doctype/contract/test_contract.js
deleted file mode 100644
index 4c77c3d649f..00000000000
--- a/erpnext/crm/doctype/contract/test_contract.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Contract", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Contract
- () => frappe.tests.make('Contract', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.js b/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.js
deleted file mode 100644
index 2a2d5e1bfc0..00000000000
--- a/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Contract Fulfilment Checklist", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Contract Fulfilment Checklist
- () => frappe.tests.make('Contract Fulfilment Checklist', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/crm/doctype/contract_template/test_contract_template.js b/erpnext/crm/doctype/contract_template/test_contract_template.js
deleted file mode 100644
index 6aaddd7df4d..00000000000
--- a/erpnext/crm/doctype/contract_template/test_contract_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Contract Template", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Contract Template
- () => frappe.tests.make('Contract Template', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/crm/doctype/market_segment/test_market_segment.js b/erpnext/crm/doctype/market_segment/test_market_segment.js
deleted file mode 100644
index aa4b868f932..00000000000
--- a/erpnext/crm/doctype/market_segment/test_market_segment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Market Segment", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Market Segment
- () => frappe.tests.make('Market Segment', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 632012b31d3..cb95881cb4c 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -95,9 +95,17 @@ frappe.ui.form.on("Opportunity", {
}, __('Create'));
}
- frm.add_custom_button(__('Quotation'),
- cur_frm.cscript.create_quotation, __('Create'));
+ if (frm.doc.opportunity_from != "Customer") {
+ frm.add_custom_button(__('Customer'),
+ function() {
+ frm.trigger("make_customer")
+ }, __('Create'));
+ }
+ frm.add_custom_button(__('Quotation'),
+ function() {
+ frm.trigger("create_quotation")
+ }, __('Create'));
}
if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) {
@@ -195,6 +203,13 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller {
frm: cur_frm
})
}
+
+ make_customer() {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.crm.doctype.opportunity.opportunity.make_customer",
+ frm: cur_frm
+ })
+ }
};
extend_cscript(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm}));
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 8ce482a3f9f..a74a94afd68 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -287,6 +287,24 @@ def make_request_for_quotation(source_name, target_doc=None):
return doclist
+@frappe.whitelist()
+def make_customer(source_name, target_doc=None):
+ def set_missing_values(source, target):
+ if source.opportunity_from == "Lead":
+ target.lead_name = source.party_name
+
+ doclist = get_mapped_doc("Opportunity", source_name, {
+ "Opportunity": {
+ "doctype": "Customer",
+ "field_map": {
+ "currency": "default_currency",
+ "customer_name": "customer_name"
+ }
+ }
+ }, target_doc, set_missing_values)
+
+ return doclist
+
@frappe.whitelist()
def make_supplier_quotation(source_name, target_doc=None):
doclist = get_mapped_doc("Opportunity", source_name, {
diff --git a/erpnext/crm/doctype/opportunity_type/test_opportunity_type.js b/erpnext/crm/doctype/opportunity_type/test_opportunity_type.js
deleted file mode 100644
index 3a1ede94dba..00000000000
--- a/erpnext/crm/doctype/opportunity_type/test_opportunity_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Opportunity Type", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Opportunity Type
- () => frappe.tests.make('Opportunity Type', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/crm/doctype/sales_stage/test_sales_stage.js b/erpnext/crm/doctype/sales_stage/test_sales_stage.js
deleted file mode 100644
index 807af1fd983..00000000000
--- a/erpnext/crm/doctype/sales_stage/test_sales_stage.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Sales Stage", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Sales Stage
- () => frappe.tests.make('Sales Stage', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/academic_year/test_academic_year.js b/erpnext/education/doctype/academic_year/test_academic_year.js
deleted file mode 100644
index 51e9cf307d8..00000000000
--- a/erpnext/education/doctype/academic_year/test_academic_year.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// Testing Setup Module in Education
-QUnit.module('education');
-
-QUnit.test('Test: Academic Year', function(assert){
- assert.expect(3);
- let done = assert.async();
- frappe.run_serially([
- () => {
- return frappe.tests.make('Academic Year', [
- {academic_year_name: '2016-17'},
- {year_start_date: '2016-07-20'},
- {year_end_date:'2017-06-20'},
- ]);
- },
-
- () => {
- assert.ok(cur_frm.doc.academic_year_name=='2016-17');
- assert.ok(cur_frm.doc.year_start_date=='2016-07-20');
- assert.ok(cur_frm.doc.year_end_date=='2017-06-20');
- },
- () => done()
- ]);
-});
diff --git a/erpnext/education/doctype/article/test_article.js b/erpnext/education/doctype/article/test_article.js
deleted file mode 100644
index 9dbf063e844..00000000000
--- a/erpnext/education/doctype/article/test_article.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Article", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Article
- () => frappe.tests.make('Article', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/content_question/test_content_question.js b/erpnext/education/doctype/content_question/test_content_question.js
deleted file mode 100644
index cc869a87fcf..00000000000
--- a/erpnext/education/doctype/content_question/test_content_question.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Content Question", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Content Question
- () => frappe.tests.make('Content Question', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/course_activity/test_course_activity.js b/erpnext/education/doctype/course_activity/test_course_activity.js
deleted file mode 100644
index c89c89e5d3c..00000000000
--- a/erpnext/education/doctype/course_activity/test_course_activity.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Activity", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Course Activity
- () => frappe.tests.make('Course Activity', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/course_content/test_course_content.js b/erpnext/education/doctype/course_content/test_course_content.js
deleted file mode 100644
index 786e67e9a37..00000000000
--- a/erpnext/education/doctype/course_content/test_course_content.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Content", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Course Content
- () => frappe.tests.make('Course Content', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/course_enrollment/test_course_enrollment.js b/erpnext/education/doctype/course_enrollment/test_course_enrollment.js
deleted file mode 100644
index 216cc307991..00000000000
--- a/erpnext/education/doctype/course_enrollment/test_course_enrollment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Enrollment", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Course Enrollment
- () => frappe.tests.make('Course Enrollment', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/course_schedule/test_course_schedule.js b/erpnext/education/doctype/course_schedule/test_course_schedule.js
deleted file mode 100644
index 5cdb67be482..00000000000
--- a/erpnext/education/doctype/course_schedule/test_course_schedule.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Schedule", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Course Schedule
- () => frappe.tests.make('Course Schedule', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/course_scheduling_tool/test_course_scheduling_tool.js b/erpnext/education/doctype/course_scheduling_tool/test_course_scheduling_tool.js
deleted file mode 100644
index 4419d181167..00000000000
--- a/erpnext/education/doctype/course_scheduling_tool/test_course_scheduling_tool.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Scheduling Tool", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Course Scheduling Tool
- () => frappe.tests.make('Course Scheduling Tool', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/course_topic/test_course_topic.js b/erpnext/education/doctype/course_topic/test_course_topic.js
deleted file mode 100644
index d8d154fb9c8..00000000000
--- a/erpnext/education/doctype/course_topic/test_course_topic.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Topic", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Course Topic
- () => frappe.tests.make('Course Topic', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/fee_category/test_fee_category.js b/erpnext/education/doctype/fee_category/test_fee_category.js
deleted file mode 100644
index a08ed33e8b6..00000000000
--- a/erpnext/education/doctype/fee_category/test_fee_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Fee Category", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Fee Category
- () => frappe.tests.make('Fee Category', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/fee_schedule/test_fee_schedule.js b/erpnext/education/doctype/fee_schedule/test_fee_schedule.js
deleted file mode 100644
index d495b4ce7b1..00000000000
--- a/erpnext/education/doctype/fee_schedule/test_fee_schedule.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Fee Schedule", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially('Fee Schedule', [
- // insert a new Fee Schedule
- () => frappe.tests.make([
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/fee_structure/test_fee_structure.js b/erpnext/education/doctype/fee_structure/test_fee_structure.js
deleted file mode 100644
index 61f41354c34..00000000000
--- a/erpnext/education/doctype/fee_structure/test_fee_structure.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Fee Structure", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Fee Structure
- () => frappe.tests.make('Fee Structure', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/program_enrollment/test_program_enrollment.js b/erpnext/education/doctype/program_enrollment/test_program_enrollment.js
deleted file mode 100644
index aea81a0714e..00000000000
--- a/erpnext/education/doctype/program_enrollment/test_program_enrollment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Program Enrollment", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Program Enrollment
- () => frappe.tests.make('Program Enrollment', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/program_enrollment_tool/test_program_enrollment_tool.js b/erpnext/education/doctype/program_enrollment_tool/test_program_enrollment_tool.js
deleted file mode 100644
index 8d55104a0f3..00000000000
--- a/erpnext/education/doctype/program_enrollment_tool/test_program_enrollment_tool.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Program Enrollment Tool", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Program Enrollment Tool
- () => frappe.tests.make('Program Enrollment Tool', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/question/test_question.js b/erpnext/education/doctype/question/test_question.js
deleted file mode 100644
index 509939c6b56..00000000000
--- a/erpnext/education/doctype/question/test_question.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Question", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Question
- () => frappe.tests.make('Question', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/quiz/test_quiz.js b/erpnext/education/doctype/quiz/test_quiz.js
deleted file mode 100644
index 147d13952ab..00000000000
--- a/erpnext/education/doctype/quiz/test_quiz.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Quiz", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Quiz
- () => frappe.tests.make('Quiz', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/quiz_activity/test_quiz_activity.js b/erpnext/education/doctype/quiz_activity/test_quiz_activity.js
deleted file mode 100644
index 94b5ab796ac..00000000000
--- a/erpnext/education/doctype/quiz_activity/test_quiz_activity.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Quiz Activity", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Quiz Activity
- () => frappe.tests.make('Quiz Activity', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/quiz_result/test_quiz_result.js b/erpnext/education/doctype/quiz_result/test_quiz_result.js
deleted file mode 100644
index 43f53a1dc77..00000000000
--- a/erpnext/education/doctype/quiz_result/test_quiz_result.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Quiz Result", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Quiz Result
- () => frappe.tests.make('Quiz Result', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/school_house/test_school_house.js b/erpnext/education/doctype/school_house/test_school_house.js
deleted file mode 100644
index dde63ecc4c6..00000000000
--- a/erpnext/education/doctype/school_house/test_school_house.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: School House", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new School House
- () => frappe.tests.make('School House', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/student/test_student.js b/erpnext/education/doctype/student/test_student.js
deleted file mode 100644
index e18d39aee07..00000000000
--- a/erpnext/education/doctype/student/test_student.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Student", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Student
- () => frappe.tests.make('Student', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/student_language/test_student_language.js b/erpnext/education/doctype/student_language/test_student_language.js
deleted file mode 100644
index 9b25569961a..00000000000
--- a/erpnext/education/doctype/student_language/test_student_language.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Student Language", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Student Language
- () => frappe.tests.make('Student Language', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/student_report_generation_tool/test_student_report_generation_tool.js b/erpnext/education/doctype/student_report_generation_tool/test_student_report_generation_tool.js
deleted file mode 100644
index 10be092bb92..00000000000
--- a/erpnext/education/doctype/student_report_generation_tool/test_student_report_generation_tool.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Student Report Generation Tool", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Student Report Generation Tool
- () => frappe.tests.make('Student Report Generation Tool', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/topic/test_topic.js b/erpnext/education/doctype/topic/test_topic.js
deleted file mode 100644
index 4460b794786..00000000000
--- a/erpnext/education/doctype/topic/test_topic.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Topic", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Topic
- () => frappe.tests.make('Topic', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/education/doctype/topic_content/test_topic_content.js b/erpnext/education/doctype/topic_content/test_topic_content.js
deleted file mode 100644
index bf9a62d0376..00000000000
--- a/erpnext/education/doctype/topic_content/test_topic_content.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Topic Content", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Topic Content
- () => frappe.tests.make('Topic Content', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.js b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.js
deleted file mode 100644
index 9c8990986e2..00000000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Amazon MWS Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Amazon MWS Settings
- () => frappe.tests.make('Amazon MWS Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js b/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js
deleted file mode 100644
index caa9399eb6c..00000000000
--- a/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: GoCardless Mandate", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new GoCardless Mandate
- () => frappe.tests.make('GoCardless Mandate', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js b/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js
deleted file mode 100644
index b6daad8de48..00000000000
--- a/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: GoCardless Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new GoCardless Settings
- () => frappe.tests.make('GoCardless Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js
deleted file mode 100644
index dc91347336d..00000000000
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Plaid Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Plaid Settings
- () => frappe.tests.make('Plaid Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js
deleted file mode 100644
index b71d7048076..00000000000
--- a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: QuickBooks Migrator", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new QuickBooks Migrator
- () => frappe.tests.make('QuickBooks Migrator', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js
deleted file mode 100644
index 433c5e2cda8..00000000000
--- a/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Tally Migration", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Tally Migration
- () => frappe.tests.make('Tally Migration', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/test_woocommerce_settings.js b/erpnext/erpnext_integrations/doctype/woocommerce_settings/test_woocommerce_settings.js
deleted file mode 100644
index ea06ab2dc4e..00000000000
--- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/test_woocommerce_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Woocommerce Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Woocommerce Settings
- () => frappe.tests.make('Woocommerce Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/erpnext_integrations/stripe_integration.py b/erpnext/erpnext_integrations/stripe_integration.py
index 108b4c0dd81..820c7405328 100644
--- a/erpnext/erpnext_integrations/stripe_integration.py
+++ b/erpnext/erpnext_integrations/stripe_integration.py
@@ -2,11 +2,12 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
-from __future__ import unicode_literals
+import stripe
+
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)
@@ -23,31 +24,38 @@ def create_stripe_subscription(gateway_controller, data):
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.")),
+ "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})
+ items = []
+ for payment_plan in stripe_settings.payment_plans:
+ plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "product_price_id")
+ items.append({"price": 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)
+ try:
+ customer = stripe.Customer.create(
+ source=stripe_settings.data.stripe_token_id,
+ description=stripe_settings.data.payer_name,
+ email=stripe_settings.data.payer_email
+ )
- if subscription.status == "active":
- stripe_settings.integration_request.db_set('status', 'Completed', update_modified=False)
- stripe_settings.flags.status_changed_to = "Completed"
+ subscription = stripe.Subscription.create(customer=customer, items=items)
- else:
- stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False)
- frappe.log_error('Subscription N°: ' + subscription.id, 'Stripe Payment not completed')
+ if subscription.status == "active":
+ stripe_settings.integration_request.db_set('status', 'Completed', update_modified=False)
+ stripe_settings.flags.status_changed_to = "Completed"
- except Exception:
+ else:
stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False)
- frappe.log_error(frappe.get_traceback())
+ 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()
+ return stripe_settings.finalize_request()
diff --git a/erpnext/healthcare/doctype/antibiotic/test_antibiotic.js b/erpnext/healthcare/doctype/antibiotic/test_antibiotic.js
deleted file mode 100644
index b92103d7509..00000000000
--- a/erpnext/healthcare/doctype/antibiotic/test_antibiotic.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Antibiotic", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Antibiotic
- () => frappe.tests.make('Antibiotic', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/appointment_type/test_appointment_type.js b/erpnext/healthcare/doctype/appointment_type/test_appointment_type.js
deleted file mode 100644
index 93274e55c7b..00000000000
--- a/erpnext/healthcare/doctype/appointment_type/test_appointment_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Appointment Type", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Appointment Type
- () => frappe.tests.make('Appointment Type', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.js
deleted file mode 100644
index 80ef3d55f2a..00000000000
--- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Clinical Procedure", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Clinical Procedure
- () => frappe.tests.make('Clinical Procedure', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.js b/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.js
deleted file mode 100644
index 1dde8b5d867..00000000000
--- a/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Clinical Procedure Template", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Clinical Procedure Template
- () => frappe.tests.make('Clinical Procedure Template', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/complaint/test_complaint.js b/erpnext/healthcare/doctype/complaint/test_complaint.js
deleted file mode 100644
index 9ff44d8da46..00000000000
--- a/erpnext/healthcare/doctype/complaint/test_complaint.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Complaint", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Complaint
- () => frappe.tests.make('Complaint', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/diagnosis/test_diagnosis.js b/erpnext/healthcare/doctype/diagnosis/test_diagnosis.js
deleted file mode 100644
index cacfef5b17d..00000000000
--- a/erpnext/healthcare/doctype/diagnosis/test_diagnosis.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Diagnosis", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Diagnosis
- () => frappe.tests.make('Diagnosis', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/dosage_form/test_dosage_form.js b/erpnext/healthcare/doctype/dosage_form/test_dosage_form.js
deleted file mode 100644
index ba54ab16faa..00000000000
--- a/erpnext/healthcare/doctype/dosage_form/test_dosage_form.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Dosage Form", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Dosage Form
- () => frappe.tests.make('Dosage Form', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json b/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json
index d91e6bf9dc6..a65c56694e7 100644
--- a/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json
+++ b/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json
@@ -56,6 +56,7 @@
"reqd": 1
},
{
+ "allow_in_quick_entry": 1,
"fieldname": "dosage_form",
"fieldtype": "Link",
"ignore_user_permissions": 1,
@@ -109,7 +110,7 @@
],
"istable": 1,
"links": [],
- "modified": "2020-09-30 23:32:09.495288",
+ "modified": "2021-06-11 11:53:06.343704",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Drug Prescription",
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.json b/erpnext/healthcare/doctype/fee_validity/fee_validity.json
index b001bf024ce..d76b42e6836 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.json
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.json
@@ -46,13 +46,13 @@
{
"fieldname": "visited",
"fieldtype": "Int",
- "label": "Visited yet",
+ "label": "Visits Completed",
"read_only": 1
},
{
"fieldname": "valid_till",
"fieldtype": "Date",
- "label": "Valid till",
+ "label": "Valid Till",
"read_only": 1
},
{
@@ -106,7 +106,7 @@
],
"in_create": 1,
"links": [],
- "modified": "2020-03-17 20:25:06.487418",
+ "modified": "2021-08-26 10:51:05.609349",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Fee Validity",
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.py b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
index 5b9c17934fa..59586e0c31b 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
@@ -11,7 +11,6 @@ import datetime
class FeeValidity(Document):
def validate(self):
self.update_status()
- self.set_start_date()
def update_status(self):
if self.visited >= self.max_visits:
@@ -19,13 +18,6 @@ class FeeValidity(Document):
else:
self.status = 'Pending'
- def set_start_date(self):
- self.start_date = getdate()
- for appointment in self.ref_appointments:
- appointment_date = frappe.db.get_value('Patient Appointment', appointment.appointment, 'appointment_date')
- if getdate(appointment_date) < self.start_date:
- self.start_date = getdate(appointment_date)
-
def create_fee_validity(appointment):
if not check_is_new_patient(appointment):
@@ -36,11 +28,9 @@ def create_fee_validity(appointment):
fee_validity.patient = appointment.patient
fee_validity.max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits') or 1
valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') or 1
- fee_validity.visited = 1
+ fee_validity.visited = 0
+ fee_validity.start_date = getdate(appointment.appointment_date)
fee_validity.valid_till = getdate(appointment.appointment_date) + datetime.timedelta(days=int(valid_days))
- fee_validity.append('ref_appointments', {
- 'appointment': appointment.name
- })
fee_validity.save(ignore_permissions=True)
return fee_validity
diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.js b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.js
deleted file mode 100644
index 0ebb97438ca..00000000000
--- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Fee Validity", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Fee Validity
- () => frappe.tests.make('Fee Validity', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
index 6ae3e12d50e..4a178723a0e 100644
--- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
@@ -22,17 +22,17 @@ class TestFeeValidity(unittest.TestCase):
item = create_healthcare_service_items()
healthcare_settings = frappe.get_single("Healthcare Settings")
healthcare_settings.enable_free_follow_ups = 1
- healthcare_settings.max_visits = 2
+ healthcare_settings.max_visits = 1
healthcare_settings.valid_days = 7
healthcare_settings.automate_appointment_invoicing = 1
healthcare_settings.op_consulting_charge_item = item
healthcare_settings.save(ignore_permissions=True)
patient, medical_department, practitioner = create_healthcare_docs()
- # appointment should not be invoiced. Check Fee Validity created for new patient
+ # For first appointment, invoice is generated. First appointment not considered in fee validity
appointment = create_appointment(patient, practitioner, nowdate())
invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced")
- self.assertEqual(invoiced, 0)
+ self.assertEqual(invoiced, 1)
# appointment should not be invoiced as it is within fee validity
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4))
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
index 8162f03f6dc..cb455eb5014 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
@@ -282,7 +282,7 @@
],
"image_field": "image",
"links": [],
- "modified": "2021-01-22 10:14:43.187675",
+ "modified": "2021-08-24 10:42:08.513054",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Practitioner",
@@ -295,6 +295,7 @@
"read": 1,
"report": 1,
"role": "Laboratory User",
+ "select": 1,
"share": 1,
"write": 1
},
@@ -307,6 +308,7 @@
"read": 1,
"report": 1,
"role": "Physician",
+ "select": 1,
"share": 1,
"write": 1
},
@@ -319,6 +321,7 @@
"read": 1,
"report": 1,
"role": "Nursing User",
+ "select": 1,
"share": 1,
"write": 1
}
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.js b/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.js
deleted file mode 100644
index 75aa208ec16..00000000000
--- a/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Healthcare Practitioner", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Healthcare Practitioner
- () => frappe.tests.make('Healthcare Practitioner', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.js b/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.js
deleted file mode 100644
index a67a4117073..00000000000
--- a/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Healthcare Service Unit", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Healthcare Service Unit
- () => frappe.tests.make('Healthcare Service Unit', [
- // 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.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js
deleted file mode 100644
index 6db8f9e9c1a..00000000000
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* 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_settings/test_healthcare_settings.js b/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.js
deleted file mode 100644
index ca10925e598..00000000000
--- a/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Healthcare Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Healthcare Settings
- () => frappe.tests.make('Healthcare Settings', [
- // 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.js b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js
deleted file mode 100644
index 1ce9afa96d4..00000000000
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* 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/lab_test/test_lab_test.js b/erpnext/healthcare/doctype/lab_test/test_lab_test.js
deleted file mode 100644
index 57cb22b2699..00000000000
--- a/erpnext/healthcare/doctype/lab_test/test_lab_test.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Lab Test", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Lab Test
- () => frappe.tests.make('Lab Test', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.js b/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.js
deleted file mode 100644
index ace60de752a..00000000000
--- a/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Lab Test Sample", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Lab Test Sample
- () => frappe.tests.make('Lab Test Sample', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.js b/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.js
deleted file mode 100644
index 7c2ec8c3483..00000000000
--- a/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Lab Test Template", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Lab Test Template
- () => frappe.tests.make('Lab Test Template', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.js b/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.js
deleted file mode 100644
index 1328dda2826..00000000000
--- a/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Lab Test UOM", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Lab Test UOM
- () => frappe.tests.make('Lab Test UOM', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/medical_code/test_medical_code.js b/erpnext/healthcare/doctype/medical_code/test_medical_code.js
deleted file mode 100644
index 8cc7c40025c..00000000000
--- a/erpnext/healthcare/doctype/medical_code/test_medical_code.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Medical Code", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Medical Code
- () => frappe.tests.make('Medical Code', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.js b/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.js
deleted file mode 100644
index 6ab6d531dfb..00000000000
--- a/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Medical Code Standard", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Medical Code Standard
- () => frappe.tests.make('Medical Code Standard', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/medical_department/test_medical_department.js b/erpnext/healthcare/doctype/medical_department/test_medical_department.js
deleted file mode 100644
index fdf49718dc8..00000000000
--- a/erpnext/healthcare/doctype/medical_department/test_medical_department.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Medical Department", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Medical Department
- () => frappe.tests.make('Medical Department', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/organism/test_organism.js b/erpnext/healthcare/doctype/organism/test_organism.js
deleted file mode 100644
index d57e5536c69..00000000000
--- a/erpnext/healthcare/doctype/organism/test_organism.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Organism", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Organism
- () => frappe.tests.make('Organism', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py
index 56a34007ffb..f77ad70633d 100644
--- a/erpnext/healthcare/doctype/patient/patient.py
+++ b/erpnext/healthcare/doctype/patient/patient.py
@@ -102,12 +102,19 @@ class Patient(Document):
return name
+ @property
+ def age(self):
+ if not self.dob:
+ return
+ dob = getdate(self.dob)
+ age = dateutil.relativedelta.relativedelta(getdate(), dob)
+ return age
+
def get_age(self):
- age_str = ''
- if self.dob:
- dob = getdate(self.dob)
- age = dateutil.relativedelta.relativedelta(getdate(), dob)
- age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)")
+ age = self.age
+ if not age:
+ return
+ age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)")
return age_str
@frappe.whitelist()
diff --git a/erpnext/healthcare/doctype/patient/test_patient.js b/erpnext/healthcare/doctype/patient/test_patient.js
deleted file mode 100644
index e1d9ecbd242..00000000000
--- a/erpnext/healthcare/doctype/patient/test_patient.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Patient", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially('Patient', [
- // insert a new Patient
- () => frappe.tests.make([
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
index 2976ef13a1d..c6e489ec17c 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
@@ -241,6 +241,13 @@ frappe.ui.form.on('Patient Appointment', {
frm.toggle_reqd('mode_of_payment', 0);
frm.toggle_reqd('paid_amount', 0);
frm.toggle_reqd('billing_item', 0);
+ } else if (data.message) {
+ frm.toggle_display('mode_of_payment', 1);
+ frm.toggle_display('paid_amount', 1);
+ frm.toggle_display('billing_item', 1);
+ frm.toggle_reqd('mode_of_payment', 1);
+ frm.toggle_reqd('paid_amount', 1);
+ frm.toggle_reqd('billing_item', 1);
} else {
// if automated appointment invoicing is disabled, hide fields
frm.toggle_display('mode_of_payment', data.message ? 1 : 0);
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index 83c92af36ac..73ec3bc325f 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
@@ -134,6 +134,7 @@
"set_only_once": 1
},
{
+ "depends_on": "eval:doc.practitioner;",
"fieldname": "section_break_12",
"fieldtype": "Section Break",
"label": "Appointment Details"
@@ -141,7 +142,6 @@
{
"fieldname": "practitioner",
"fieldtype": "Link",
- "ignore_user_permissions": 1,
"in_standard_filter": 1,
"label": "Healthcare Practitioner",
"options": "Healthcare Practitioner",
@@ -349,7 +349,7 @@
}
],
"links": [],
- "modified": "2021-02-08 13:13:15.116833",
+ "modified": "2021-06-16 00:40:26.841794",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Appointment",
@@ -400,4 +400,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index cdd4ad39c88..7db4fa616ad 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -109,9 +109,13 @@ class PatientAppointment(Document):
frappe.db.set_value('Patient Appointment', self.name, 'notes', comments)
def update_fee_validity(self):
+ if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'):
+ return
+
fee_validity = manage_fee_validity(self)
if fee_validity:
- frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
+ frappe.msgprint(_('{0}: {1} has fee validity till {2}').format(self.patient,
+ frappe.bold(self.patient_name), fee_validity.valid_till))
@frappe.whitelist()
def get_therapy_types(self):
@@ -135,8 +139,6 @@ def check_payment_fields_reqd(patient):
fee_validity = frappe.db.exists('Fee Validity', {'patient': patient, 'status': 'Pending'})
if fee_validity:
return {'fee_validity': fee_validity}
- if check_is_new_patient(patient):
- return False
return True
return False
@@ -151,8 +153,6 @@ def invoice_appointment(appointment_doc):
elif not fee_validity:
if frappe.db.exists('Fee Validity Reference', {'appointment': appointment_doc.name}):
return
- if check_is_new_patient(appointment_doc.patient, appointment_doc.name):
- return
else:
fee_validity = None
@@ -196,9 +196,7 @@ def check_is_new_patient(patient, name=None):
filters['name'] = ('!=', name)
has_previous_appointment = frappe.db.exists('Patient Appointment', filters)
- if has_previous_appointment:
- return False
- return True
+ return not has_previous_appointment
def get_appointment_item(appointment_doc, item):
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.js
deleted file mode 100644
index 71fc1778455..00000000000
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Patient Appointment", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Patient Appointment
- () => frappe.tests.make('Patient Appointment', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index 9dd4a2c73c0..157b3e11624 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -4,11 +4,12 @@
from __future__ import unicode_literals
import unittest
import frappe
-from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
+from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter, check_payment_fields_reqd, check_is_new_patient
from frappe.utils import nowdate, add_days, now_datetime
from frappe.utils.make_random import get_random
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
+
class TestPatientAppointment(unittest.TestCase):
def setUp(self):
frappe.db.sql("""delete from `tabPatient Appointment`""")
@@ -106,14 +107,17 @@ class TestPatientAppointment(unittest.TestCase):
patient, medical_department, practitioner = create_healthcare_docs()
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1)
appointment = create_appointment(patient, practitioner, nowdate())
- fee_validity = frappe.db.get_value('Fee Validity Reference', {'appointment': appointment.name}, 'parent')
+ fee_validity = frappe.db.get_value('Fee Validity', {'patient': patient, 'practitioner': practitioner})
# fee validity created
self.assertTrue(fee_validity)
- visited = frappe.db.get_value('Fee Validity', fee_validity, 'visited')
+ # first follow up appointment
+ appointment = create_appointment(patient, practitioner, nowdate())
+ self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 1)
+
update_status(appointment.name, 'Cancelled')
# check fee validity updated
- self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), visited - 1)
+ self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 0)
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
@@ -176,10 +180,55 @@ class TestPatientAppointment(unittest.TestCase):
mark_invoiced_inpatient_occupancy(ip_record1)
discharge_patient(ip_record1)
+ def test_payment_should_be_mandatory_for_new_patient_appointment(self):
+ frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1)
+ frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
+ frappe.db.set_value('Healthcare Settings', None, 'max_visits', 3)
+ frappe.db.set_value('Healthcare Settings', None, 'valid_days', 30)
+
+ patient = create_patient()
+ assert check_is_new_patient(patient)
+ payment_required = check_payment_fields_reqd(patient)
+ assert payment_required is True
+
+ def test_sales_invoice_should_be_generated_for_new_patient_appointment(self):
+ patient, medical_department, practitioner = create_healthcare_docs()
+ frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
+ invoice_count = frappe.db.count('Sales Invoice')
+
+ assert check_is_new_patient(patient)
+ create_appointment(patient, practitioner, nowdate())
+ new_invoice_count = frappe.db.count('Sales Invoice')
+
+ assert new_invoice_count == invoice_count + 1
+
+ def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self):
+ patient, medical_department, practitioner = create_healthcare_docs()
+ create_appointment(patient, practitioner, nowdate())
+
+ patient, medical_department, new_practitioner = create_healthcare_docs(practitioner_name='Dr. John')
+ create_appointment(patient, new_practitioner, nowdate())
+
+ roles = [{"doctype": "Has Role", "role": "Physician"}]
+ user = create_user(roles=roles)
+ new_practitioner = frappe.get_doc('Healthcare Practitioner', new_practitioner)
+ new_practitioner.user_id = user.email
+ new_practitioner.save()
+
+ frappe.set_user(user.name)
+ appointments = frappe.get_list('Patient Appointment')
+ assert len(appointments) == 1
+
+ frappe.set_user("Administrator")
+ appointments = frappe.get_list('Patient Appointment')
+ assert len(appointments) == 2
+
+def create_healthcare_docs(practitioner_name=None):
+ if not practitioner_name:
+ practitioner_name = '_Test Healthcare Practitioner'
-def create_healthcare_docs():
patient = create_patient()
- practitioner = frappe.db.exists('Healthcare Practitioner', '_Test Healthcare Practitioner')
+ practitioner = frappe.db.exists('Healthcare Practitioner', practitioner_name)
medical_department = frappe.db.exists('Medical Department', '_Test Medical Department')
if not medical_department:
@@ -190,7 +239,7 @@ def create_healthcare_docs():
if not practitioner:
practitioner = frappe.new_doc('Healthcare Practitioner')
- practitioner.first_name = '_Test Healthcare Practitioner'
+ practitioner.first_name = practitioner_name
practitioner.gender = 'Female'
practitioner.department = medical_department
practitioner.op_consulting_charge = 500
@@ -296,3 +345,17 @@ def create_appointment_type(args=None):
'price_list': args.get('price_list') or frappe.db.get_value("Price List", {"selling": 1}),
'items': args.get('items') or items
}).insert()
+
+def create_user(email=None, roles=None):
+ if not email:
+ email = '{}@frappe.com'.format(frappe.utils.random_string(10))
+ user = frappe.db.exists('User', email)
+ if not user:
+ user = frappe.get_doc({
+ "doctype": "User",
+ "email": email,
+ "first_name": "test_user",
+ "password": "password",
+ "roles": roles,
+ }).insert()
+ return user
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
index aaeaa692e63..c3466260d2b 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
@@ -185,7 +185,42 @@ frappe.ui.form.on('Patient Encounter', {
};
frm.set_value(values);
}
- }
+ },
+
+ get_applicable_treatment_plans: function(frm) {
+ frappe.call({
+ method: 'get_applicable_treatment_plans',
+ doc: frm.doc,
+ args: {'encounter': frm.doc},
+ freeze: true,
+ freeze_message: __('Fetching Treatment Plans'),
+ callback: function() {
+ new frappe.ui.form.MultiSelectDialog({
+ doctype: "Treatment Plan Template",
+ target: this.cur_frm,
+ setters: {
+ medical_department: "",
+ },
+ action(selections) {
+ frappe.call({
+ method: 'set_treatment_plans',
+ doc: frm.doc,
+ args: selections,
+ }).then(() => {
+ frm.refresh_field('drug_prescription');
+ frm.refresh_field('procedure_prescription');
+ frm.refresh_field('lab_test_prescription');
+ frm.refresh_field('therapies');
+ });
+ cur_dialog.hide();
+ }
+ });
+
+
+ }
+ });
+ },
+
});
var schedule_inpatient = function(frm) {
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
index b646ff9ebe6..994597dca7c 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
@@ -31,6 +31,7 @@
"sb_symptoms",
"symptoms",
"symptoms_in_print",
+ "get_applicable_treatment_plans",
"physical_examination",
"diagnosis",
"diagnosis_in_print",
@@ -324,11 +325,17 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.patient",
+ "fieldname": "get_applicable_treatment_plans",
+ "fieldtype": "Button",
+ "label": "Get Applicable Treatment Plans"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-11-30 10:39:00.783119",
+ "modified": "2021-07-27 11:39:12.347704",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Encounter",
@@ -358,4 +365,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
index 2b3029efdeb..7a745ae468e 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
@@ -10,6 +10,7 @@ from frappe.utils import cstr, getdate, add_days
from frappe import _
from frappe.model.mapper import get_mapped_doc
+
class PatientEncounter(Document):
def validate(self):
self.set_title()
@@ -33,6 +34,85 @@ class PatientEncounter(Document):
self.title = _('{0} with {1}').format(self.patient_name or self.patient,
self.practitioner_name or self.practitioner)[:100]
+ @frappe.whitelist()
+ @staticmethod
+ def get_applicable_treatment_plans(encounter):
+ patient = frappe.get_doc('Patient', encounter['patient'])
+
+ plan_filters = {}
+ plan_filters['name'] = ['in', []]
+
+ age = patient.age
+ if age:
+ plan_filters['patient_age_from'] = ['<=', age.years]
+ plan_filters['patient_age_to'] = ['>=', age.years]
+
+ gender = patient.sex
+ if gender:
+ plan_filters['gender'] = ['in', [gender, None]]
+
+ diagnosis = encounter.get('diagnosis')
+ if diagnosis:
+ diagnosis = [_diagnosis['diagnosis'] for _diagnosis in encounter['diagnosis']]
+ filters = [
+ ['diagnosis', 'in', diagnosis],
+ ['parenttype', '=', 'Treatment Plan Template'],
+ ]
+ diagnosis = frappe.get_list('Patient Encounter Diagnosis', filters=filters, fields='*')
+ plan_names = [_diagnosis['parent'] for _diagnosis in diagnosis]
+ plan_filters['name'][1].extend(plan_names)
+
+ symptoms = encounter.get('symptoms')
+ if symptoms:
+ symptoms = [symptom['complaint'] for symptom in encounter['symptoms']]
+ filters = [
+ ['complaint', 'in', symptoms],
+ ['parenttype', '=', 'Treatment Plan Template'],
+ ]
+ symptoms = frappe.get_list('Patient Encounter Symptom', filters=filters, fields='*')
+ plan_names = [symptom['parent'] for symptom in symptoms]
+ plan_filters['name'][1].extend(plan_names)
+
+ if not plan_filters['name'][1]:
+ plan_filters.pop('name')
+
+ plans = frappe.get_list('Treatment Plan Template', fields='*', filters=plan_filters)
+
+ return plans
+
+ @frappe.whitelist()
+ def set_treatment_plans(self, treatment_plans=None):
+ for treatment_plan in treatment_plans:
+ self.set_treatment_plan(treatment_plan)
+
+ def set_treatment_plan(self, plan):
+ plan_items = frappe.get_list('Treatment Plan Template Item', filters={'parent': plan}, fields='*')
+ for plan_item in plan_items:
+ self.set_treatment_plan_item(plan_item)
+
+ drugs = frappe.get_list('Drug Prescription', filters={'parent': plan}, fields='*')
+ for drug in drugs:
+ self.append('drug_prescription', drug)
+
+ self.save()
+
+ def set_treatment_plan_item(self, plan_item):
+ if plan_item.type == 'Clinical Procedure Template':
+ self.append('procedure_prescription', {
+ 'procedure': plan_item.template
+ })
+
+ if plan_item.type == 'Lab Test Template':
+ self.append('lab_test_prescription', {
+ 'lab_test_code': plan_item.template
+ })
+
+ if plan_item.type == 'Therapy Type':
+ self.append('therapies', {
+ 'therapy_type': plan_item.template
+ })
+
+
@frappe.whitelist()
def make_ip_medication_order(source_name, target_doc=None):
def set_missing_values(source, target):
diff --git a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.js
deleted file mode 100644
index 1baabf7eef7..00000000000
--- a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Patient Encounter", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Patient Encounter
- () => frappe.tests.make('Patient Encounter', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py
index f5df152050f..96976821a71 100644
--- a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py
+++ b/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py
@@ -4,5 +4,82 @@
from __future__ import unicode_literals
import unittest
+import frappe
+from erpnext.healthcare.doctype.patient_encounter.patient_encounter import PatientEncounter
+
+
class TestPatientEncounter(unittest.TestCase):
- pass
+ def setUp(self):
+ try:
+ gender_m = frappe.get_doc({
+ 'doctype': 'Gender',
+ 'gender': 'MALE'
+ }).insert()
+ gender_f = frappe.get_doc({
+ 'doctype': 'Gender',
+ 'gender': 'FEMALE'
+ }).insert()
+ except frappe.exceptions.DuplicateEntryError:
+ gender_m = frappe.get_doc({
+ 'doctype': 'Gender',
+ 'gender': 'MALE'
+ })
+ gender_f = frappe.get_doc({
+ 'doctype': 'Gender',
+ 'gender': 'FEMALE'
+ })
+
+ self.patient_male = frappe.get_doc({
+ 'doctype': 'Patient',
+ 'first_name': 'John',
+ 'sex': gender_m.gender,
+ }).insert()
+ self.patient_female = frappe.get_doc({
+ 'doctype': 'Patient',
+ 'first_name': 'Curie',
+ 'sex': gender_f.gender,
+ }).insert()
+ self.practitioner = frappe.get_doc({
+ 'doctype': 'Healthcare Practitioner',
+ 'first_name': 'Doc',
+ 'sex': 'MALE',
+ }).insert()
+ try:
+ self.care_plan_male = frappe.get_doc({
+ 'doctype': 'Treatment Plan Template',
+ 'template_name': 'test plan - m',
+ 'gender': gender_m.gender,
+ }).insert()
+ self.care_plan_female = frappe.get_doc({
+ 'doctype': 'Treatment Plan Template',
+ 'template_name': 'test plan - f',
+ 'gender': gender_f.gender,
+ }).insert()
+ except frappe.exceptions.DuplicateEntryError:
+ self.care_plan_male = frappe.get_doc({
+ 'doctype': 'Treatment Plan Template',
+ 'template_name': 'test plan - m',
+ 'gender': gender_m.gender,
+ })
+ self.care_plan_female = frappe.get_doc({
+ 'doctype': 'Treatment Plan Template',
+ 'template_name': 'test plan - f',
+ 'gender': gender_f.gender,
+ })
+
+ def test_treatment_plan_template_filter(self):
+ encounter = frappe.get_doc({
+ 'doctype': 'Patient Encounter',
+ 'patient': self.patient_male.name,
+ 'practitioner': self.practitioner.name,
+ }).insert()
+ plans = PatientEncounter.get_applicable_treatment_plans(encounter.as_dict())
+ self.assertEqual(plans[0]['name'], self.care_plan_male.template_name)
+
+ encounter = frappe.get_doc({
+ 'doctype': 'Patient Encounter',
+ 'patient': self.patient_female.name,
+ 'practitioner': self.practitioner.name,
+ }).insert()
+ plans = PatientEncounter.get_applicable_treatment_plans(encounter.as_dict())
+ self.assertEqual(plans[0]['name'], self.care_plan_female.template_name)
diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.js b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.js
deleted file mode 100644
index 66dda09e256..00000000000
--- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Patient Medical Record", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Patient Medical Record
- () => frappe.tests.make('Patient Medical Record', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.js b/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.js
deleted file mode 100644
index 32dac2c6526..00000000000
--- a/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Practitioner Schedule", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Practitioner Schedule
- () => frappe.tests.make('Practitioner Schedule', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.js b/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.js
deleted file mode 100644
index 009614ff5dd..00000000000
--- a/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Prescription Dosage", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Prescription Dosage
- () => frappe.tests.make('Prescription Dosage', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.js b/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.js
deleted file mode 100644
index 4971e79198c..00000000000
--- a/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Prescription Duration", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Prescription Duration
- () => frappe.tests.make('Prescription Duration', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/sample_collection/test_sample_collection.js b/erpnext/healthcare/doctype/sample_collection/test_sample_collection.js
deleted file mode 100644
index 2b4aed756bf..00000000000
--- a/erpnext/healthcare/doctype/sample_collection/test_sample_collection.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Sample Collection", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Sample Collection
- () => frappe.tests.make('Sample Collection', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/sensitivity/test_sensitivity.js b/erpnext/healthcare/doctype/sensitivity/test_sensitivity.js
deleted file mode 100644
index c2cf406f96d..00000000000
--- a/erpnext/healthcare/doctype/sensitivity/test_sensitivity.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Sensitivity", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Sensitivity
- () => frappe.tests.make('Sensitivity', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/test_records.json b/erpnext/healthcare/doctype/treatment_plan_template/test_records.json
new file mode 100644
index 00000000000..d661b4304f6
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/test_records.json
@@ -0,0 +1,7 @@
+[
+ {
+ "doctype": "Treatment Plan Template",
+ "template_name": "Chemo",
+ "patient_age_from": 21
+ }
+]
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py b/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py
new file mode 100644
index 00000000000..21ede7129f1
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestTreatmentPlanTemplate(unittest.TestCase):
+ pass
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js
new file mode 100644
index 00000000000..986c3cb6e42
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Treatment Plan Template', {
+ refresh: function (frm) {
+ frm.set_query('type', 'items', function () {
+ return {
+ filters: {
+ 'name': ['in', ['Lab Test Template', 'Clinical Procedure Template', 'Therapy Type']],
+ }
+ };
+ });
+ },
+});
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json
new file mode 100644
index 00000000000..85a312fb174
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json
@@ -0,0 +1,189 @@
+{
+ "actions": [],
+ "autoname": "field:template_name",
+ "creation": "2021-06-10 10:14:17.901273",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "section_break_1",
+ "template_name",
+ "description",
+ "practitioners",
+ "disabled",
+ "column_break_1",
+ "medical_department",
+ "goal",
+ "order_group",
+ "section_break_8",
+ "patient_age_from",
+ "complaints",
+ "gender",
+ "column_break_12",
+ "patient_age_to",
+ "diagnosis",
+ "plan_items_section",
+ "items",
+ "drugs"
+ ],
+ "fields": [
+ {
+ "fieldname": "section_break_1",
+ "fieldtype": "Section Break",
+ "label": "Plan Details"
+ },
+ {
+ "fieldname": "medical_department",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Medical Department",
+ "options": "Medical Department"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "Description"
+ },
+ {
+ "fieldname": "goal",
+ "fieldtype": "Small Text",
+ "label": "Goal"
+ },
+ {
+ "fieldname": "practitioners",
+ "fieldtype": "Table MultiSelect",
+ "label": "Practitioners",
+ "options": "Treatment Plan Template Practitioner"
+ },
+ {
+ "fieldname": "order_group",
+ "fieldtype": "Link",
+ "label": "Order Group",
+ "options": "Patient Encounter",
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "label": "Plan Conditions"
+ },
+ {
+ "fieldname": "template_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Template Name",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "patient_age_from",
+ "fieldtype": "Int",
+ "label": "Patient Age From",
+ "non_negative": 1
+ },
+ {
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "patient_age_to",
+ "fieldtype": "Int",
+ "label": "Patient Age To",
+ "non_negative": 1
+ },
+ {
+ "fieldname": "gender",
+ "fieldtype": "Link",
+ "label": "Gender",
+ "options": "Gender"
+ },
+ {
+ "fieldname": "complaints",
+ "fieldtype": "Table MultiSelect",
+ "label": "Complaints",
+ "options": "Patient Encounter Symptom"
+ },
+ {
+ "fieldname": "diagnosis",
+ "fieldtype": "Table MultiSelect",
+ "label": "Diagnosis",
+ "options": "Patient Encounter Diagnosis"
+ },
+ {
+ "fieldname": "plan_items_section",
+ "fieldtype": "Section Break",
+ "label": "Plan Items"
+ },
+ {
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "label": "Items",
+ "options": "Treatment Plan Template Item"
+ },
+ {
+ "fieldname": "drugs",
+ "fieldtype": "Table",
+ "label": "Drugs",
+ "options": "Drug Prescription"
+ },
+ {
+ "default": "0",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "label": "Disabled"
+ },
+ {
+ "fieldname": "column_break_1",
+ "fieldtype": "Column Break"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-08-18 02:41:58.354296",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Treatment Plan Template",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Physician",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Healthcare Administrator",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "template_name",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py
new file mode 100644
index 00000000000..a92e2668fe8
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+class TreatmentPlanTemplate(Document):
+ def validate(self):
+ self.validate_age()
+
+ def validate_age(self):
+ if self.patient_age_from and self.patient_age_from < 0:
+ frappe.throw(_('Patient Age From cannot be less than 0'))
+ if self.patient_age_to and self.patient_age_to < 0:
+ frappe.throw(_('Patient Age To cannot be less than 0'))
+ if self.patient_age_to and self.patient_age_from and \
+ self.patient_age_to < self.patient_age_from:
+ frappe.throw(_('Patient Age To cannot be less than Patient Age From'))
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js
new file mode 100644
index 00000000000..7ab31dff791
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js
@@ -0,0 +1,10 @@
+frappe.listview_settings['Treatment Plan Template'] = {
+ get_indicator: function(doc) {
+ var colors = {
+ 1: 'gray',
+ 0: 'blue',
+ };
+ let label = doc.disabled == 1 ? 'Disabled' : 'Enabled';
+ return [__(label), colors[doc.disabled], 'disable,=,' + doc.disabled];
+ }
+};
diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template_item/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json
new file mode 100644
index 00000000000..20a9d6793a5
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json
@@ -0,0 +1,55 @@
+{
+ "actions": [],
+ "creation": "2021-06-10 11:47:29.194795",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "type",
+ "template",
+ "qty",
+ "instructions"
+ ],
+ "fields": [
+ {
+ "fieldname": "type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Type",
+ "options": "DocType",
+ "reqd": 1
+ },
+ {
+ "fieldname": "template",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Template",
+ "options": "type",
+ "reqd": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "qty",
+ "fieldtype": "Int",
+ "label": "Qty"
+ },
+ {
+ "fieldname": "instructions",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Instructions"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-08-17 11:19:03.515441",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Treatment Plan Template Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py
new file mode 100644
index 00000000000..5f58b06af60
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class TreatmentPlanTemplateItem(Document):
+ pass
diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json
new file mode 100644
index 00000000000..04da387f7b8
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json
@@ -0,0 +1,32 @@
+{
+ "actions": [],
+ "creation": "2021-06-10 10:37:56.669416",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "practitioner"
+ ],
+ "fields": [
+ {
+ "fieldname": "practitioner",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Practitioner",
+ "options": "Healthcare Practitioner",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-06-11 16:05:06.733299",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Treatment Plan Template Practitioner",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py
new file mode 100644
index 00000000000..6d34568e15c
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class TreatmentPlanTemplatePractitioner(Document):
+ pass
diff --git a/erpnext/healthcare/doctype/vital_signs/test_vital_signs.js b/erpnext/healthcare/doctype/vital_signs/test_vital_signs.js
deleted file mode 100644
index f4ab4466be4..00000000000
--- a/erpnext/healthcare/doctype/vital_signs/test_vital_signs.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Vital Signs", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Vital Signs
- () => frappe.tests.make('Vital Signs', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room/test_hotel_room.js b/erpnext/hotels/doctype/hotel_room/test_hotel_room.js
deleted file mode 100644
index 8b2b83330f3..00000000000
--- a/erpnext/hotels/doctype/hotel_room/test_hotel_room.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Hotel Room
- () => frappe.tests.make('Hotel Room', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js
deleted file mode 100644
index f1ebad41d41..00000000000
--- a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room Package", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Hotel Room Package
- () => frappe.tests.make('Hotel Room Package', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js
deleted file mode 100644
index ba0d1fd3e96..00000000000
--- a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room Pricing", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Hotel Room Pricing
- () => frappe.tests.make('Hotel Room Pricing', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js
deleted file mode 100644
index 73a561c408c..00000000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room Pricing Package", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Hotel Room Pricing Package
- () => frappe.tests.make('Hotel Room Pricing Package', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js
deleted file mode 100644
index 2897139359b..00000000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room Reservation", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Hotel Room Reservation
- () => frappe.tests.make('Hotel Room Reservation', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js
deleted file mode 100644
index e2dd5780e3c..00000000000
--- a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room Type", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Hotel Room Type
- () => frappe.tests.make('Hotel Room Type', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js
deleted file mode 100644
index bc0b7f8341d..00000000000
--- a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Hotel Settings
- () => frappe.tests.make('Hotel Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.js b/erpnext/hr/doctype/attendance_request/test_attendance_request.js
deleted file mode 100644
index d40ec61b086..00000000000
--- a/erpnext/hr/doctype/attendance_request/test_attendance_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Attendance Request", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Attendance Request
- () => frappe.tests.make('Attendance Request', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/branch/test_branch.js b/erpnext/hr/doctype/branch/test_branch.js
deleted file mode 100644
index 82a6ae103ee..00000000000
--- a/erpnext/hr/doctype/branch/test_branch.js
+++ /dev/null
@@ -1,23 +0,0 @@
-QUnit.module('hr');
-
-QUnit.test("Test: Branch [HR]", function (assert) {
- assert.expect(1);
- let done = assert.async();
-
- frappe.run_serially([
- // test branch creation
- () => frappe.set_route("List", "Branch", "List"),
- () => frappe.new_doc("Branch"),
- () => frappe.timeout(1),
- () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(),
- () => frappe.timeout(1),
- () => cur_frm.set_value("branch", "Test Branch"),
-
- // save form
- () => cur_frm.save(),
- () => frappe.timeout(1),
- () => assert.equal("Test Branch", cur_frm.doc.branch,
- 'name of branch correctly saved'),
- () => done()
- ]);
-});
diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js
deleted file mode 100644
index bebcaac400e..00000000000
--- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Compensatory Leave Request", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Compensatory Leave Request
- () => frappe.tests.make('Compensatory Leave Request', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js
deleted file mode 100644
index 15335171473..00000000000
--- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Daily Work Summary", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Daily Work Summary
- () => frappe.tests.make('Daily Work Summary', [
- // values to be set
- { key: 'value' }
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/department/test_department.js b/erpnext/hr/doctype/department/test_department.js
deleted file mode 100644
index e73779c97c6..00000000000
--- a/erpnext/hr/doctype/department/test_department.js
+++ /dev/null
@@ -1,23 +0,0 @@
-QUnit.module('hr');
-
-QUnit.test("Test: Department [HR]", function (assert) {
- assert.expect(1);
- let done = assert.async();
-
- frappe.run_serially([
- // test department creation
- () => frappe.set_route("List", "Department", "List"),
- () => frappe.new_doc("Department"),
- () => frappe.timeout(1),
- () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(),
- () => frappe.timeout(1),
- () => cur_frm.set_value("department_name", "Test Department"),
- () => cur_frm.set_value("leave_block_list", "Test Leave block list"),
- // save form
- () => cur_frm.save(),
- () => frappe.timeout(1),
- () => assert.equal("Test Department", cur_frm.doc.department_name,
- 'name of department correctly saved'),
- () => done()
- ]);
-});
diff --git a/erpnext/hr/doctype/designation/test_designation.js b/erpnext/hr/doctype/designation/test_designation.js
deleted file mode 100644
index 00adf8293f7..00000000000
--- a/erpnext/hr/doctype/designation/test_designation.js
+++ /dev/null
@@ -1,23 +0,0 @@
-QUnit.module('hr');
-
-QUnit.test("Test: Designation [HR]", function (assert) {
- assert.expect(1);
- let done = assert.async();
-
- frappe.run_serially([
- // test designation creation
- () => frappe.set_route("List", "Designation", "List"),
- () => frappe.new_doc("Designation"),
- () => frappe.timeout(1),
- () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(),
- () => frappe.timeout(1),
- () => cur_frm.set_value("designation_name", "Test Designation"),
- () => cur_frm.set_value("description", "This designation is just for testing."),
- // save form
- () => cur_frm.save(),
- () => frappe.timeout(1),
- () => assert.equal("Test Designation", cur_frm.doc.designation_name,
- 'name of designation correctly saved'),
- () => done()
- ]);
-});
diff --git a/erpnext/hr/doctype/driver/test_driver.js b/erpnext/hr/doctype/driver/test_driver.js
deleted file mode 100644
index ff9f61e66a6..00000000000
--- a/erpnext/hr/doctype/driver/test_driver.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Driver", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Driver
- () => frappe.tests.make('Driver', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.js b/erpnext/hr/doctype/employee_advance/test_employee_advance.js
deleted file mode 100644
index 1b9ec6f6d0c..00000000000
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Advance", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Advance
- () => frappe.tests.make('Employee Advance', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
index 65792b42fb8..044a5a9886b 100644
--- a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
+++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2018-05-09 05:37:18.439763",
"doctype": "DocType",
"editable_grid": 1,
@@ -7,6 +8,8 @@
"activity_name",
"user",
"role",
+ "begin_on",
+ "duration",
"column_break_3",
"task",
"task_weight",
@@ -16,12 +19,16 @@
],
"fields": [
{
+ "columns": 3,
"fieldname": "activity_name",
"fieldtype": "Data",
"in_list_view": 1,
- "label": "Activity Name"
+ "label": "Activity Name",
+ "reqd": 1
},
{
+ "columns": 2,
+ "depends_on": "eval:!doc.role",
"fieldname": "user",
"fieldtype": "Link",
"in_list_view": 1,
@@ -29,9 +36,10 @@
"options": "User"
},
{
+ "columns": 1,
+ "depends_on": "eval:!doc.user",
"fieldname": "role",
"fieldtype": "Link",
- "in_list_view": 1,
"label": "Role",
"options": "Role"
},
@@ -67,10 +75,25 @@
"fieldname": "description",
"fieldtype": "Text Editor",
"label": "Description"
+ },
+ {
+ "columns": 2,
+ "fieldname": "duration",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Duration (Days)"
+ },
+ {
+ "columns": 2,
+ "fieldname": "begin_on",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Begin On (Days)"
}
],
"istable": 1,
- "modified": "2019-06-03 19:22:42.965762",
+ "links": [],
+ "modified": "2021-07-30 15:55:22.470102",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Boarding Activity",
diff --git a/erpnext/hr/doctype/employee_grade/test_employee_grade.js b/erpnext/hr/doctype/employee_grade/test_employee_grade.js
deleted file mode 100644
index d684fb2ad1b..00000000000
--- a/erpnext/hr/doctype/employee_grade/test_employee_grade.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Grade", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Grade
- () => frappe.tests.make('Employee Grade', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js b/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js
deleted file mode 100644
index 245cb32971f..00000000000
--- a/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Health Insurance", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Health Insurance
- () => frappe.tests.make('Employee Health Insurance', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
index 673e228395e..fd877a68d85 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
@@ -8,20 +8,24 @@
"field_order": [
"job_applicant",
"job_offer",
- "employee_name",
- "employee",
- "date_of_joining",
- "boarding_status",
- "notify_users_by_email",
- "column_break_7",
"employee_onboarding_template",
+ "column_break_7",
"company",
+ "boarding_status",
+ "project",
+ "details_section",
+ "employee",
+ "employee_name",
"department",
"designation",
"employee_grade",
- "project",
+ "holiday_list",
+ "column_break_13",
+ "date_of_joining",
+ "boarding_begins_on",
"table_for_activity",
"activities",
+ "notify_users_by_email",
"amended_from"
],
"fields": [
@@ -58,7 +62,8 @@
"fieldname": "date_of_joining",
"fieldtype": "Date",
"in_list_view": 1,
- "label": "Date of Joining"
+ "label": "Date of Joining",
+ "reqd": 1
},
{
"allow_on_submit": 1,
@@ -90,7 +95,8 @@
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
- "options": "Company"
+ "options": "Company",
+ "reqd": 1
},
{
"fieldname": "department",
@@ -121,7 +127,8 @@
},
{
"fieldname": "table_for_activity",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Onboarding Activities"
},
{
"allow_on_submit": 1,
@@ -138,11 +145,32 @@
"options": "Employee Onboarding",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "details_section",
+ "fieldtype": "Section Break",
+ "label": "Employee Details"
+ },
+ {
+ "fieldname": "column_break_13",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "boarding_begins_on",
+ "fieldtype": "Date",
+ "label": "Onboarding Begins On",
+ "reqd": 1
+ },
+ {
+ "fieldname": "holiday_list",
+ "fieldtype": "Link",
+ "label": "Holiday List",
+ "options": "Holiday List"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2021-06-03 18:01:51.097927",
+ "modified": "2021-07-30 14:55:04.560683",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Onboarding",
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js
deleted file mode 100644
index d15cef77dca..00000000000
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Onboarding", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Onboarding
- () => frappe.tests.make('Employee Onboarding', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
index 0445270b9ff..ea46aa24a6c 100644
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
@@ -5,8 +5,9 @@ from __future__ import unicode_literals
import frappe
import unittest
-from frappe.utils import nowdate
+from frappe.utils import getdate
from erpnext.hr.doctype.employee_onboarding.employee_onboarding import make_employee
+from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
from erpnext.hr.doctype.employee_onboarding.employee_onboarding import IncompleteTaskError
from erpnext.hr.doctype.job_offer.test_job_offer import create_job_offer
@@ -46,7 +47,7 @@ class TestEmployeeOnboarding(unittest.TestCase):
onboarding.reload()
employee = make_employee(onboarding.name)
employee.first_name = employee.employee_name
- employee.date_of_joining = nowdate()
+ employee.date_of_joining = getdate()
employee.date_of_birth = '1990-05-08'
employee.gender = 'Female'
employee.insert()
@@ -82,11 +83,14 @@ def get_job_offer(applicant_name):
def create_employee_onboarding():
applicant = get_job_applicant()
job_offer = get_job_offer(applicant.name)
+ holiday_list = make_holiday_list()
onboarding = frappe.new_doc('Employee Onboarding')
onboarding.job_applicant = applicant.name
onboarding.job_offer = job_offer.name
+ onboarding.date_of_joining = onboarding.boarding_begins_on = getdate()
onboarding.company = '_Test Company'
+ onboarding.holiday_list = holiday_list
onboarding.designation = 'Researcher'
onboarding.append('activities', {
'activity_name': 'Assign ID Card',
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json
deleted file mode 100644
index 4e91b723844..00000000000
--- a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json
+++ /dev/null
@@ -1,290 +0,0 @@
-{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-05-09 05:37:18.439763",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "activity_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Activity 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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "user",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "User",
- "length": 0,
- "no_copy": 0,
- "options": "User",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "role",
- "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": "Role",
- "length": 0,
- "no_copy": 0,
- "options": "Role",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval: doc.parenttype == \"Employee Onboarding\"",
- "fieldname": "completed",
- "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": "Completed",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "required_for_employee_creation",
- "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": "Required for Employee Creation",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_6",
- "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "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": "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
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-05-09 06:15:41.768236",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Employee Onboarding Activity",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py
deleted file mode 100644
index d1706318191..00000000000
--- a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- 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.model.document import Document
-
-class EmployeeOnboardingActivity(Document):
- pass
diff --git a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js b/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js
deleted file mode 100644
index 10912edb6ac..00000000000
--- a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Onboarding Template", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Onboarding Template
- () => frappe.tests.make('Employee Onboarding Template', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js b/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js
deleted file mode 100644
index 5f0a5baf818..00000000000
--- a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Promotion", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Promotion
- () => frappe.tests.make('Employee Promotion', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json
index c10da5c35e7..c240493e820 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.json
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.json
@@ -15,6 +15,7 @@
"company",
"boarding_status",
"resignation_letter_date",
+ "boarding_begins_on",
"project",
"table_for_activity",
"employee_separation_template",
@@ -144,11 +145,17 @@
"options": "Employee Separation",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "boarding_begins_on",
+ "fieldtype": "Date",
+ "label": "Separation Begins On",
+ "reqd": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2021-06-03 18:02:54.007313",
+ "modified": "2021-07-30 14:03:51.218791",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Separation",
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.js b/erpnext/hr/doctype/employee_separation/test_employee_separation.js
deleted file mode 100644
index d6c635951f7..00000000000
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Separation", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Separation
- () => frappe.tests.make('Employee Separation', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
index d63501a9314..2c11cbbe2c7 100644
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
+from frappe.utils import getdate
import unittest
test_dependencies = ['Employee Onboarding']
@@ -34,9 +35,10 @@ class TestEmployeeSeparation(unittest.TestCase):
doc.delete()
def create_employee_separation():
- employee = frappe.db.get_value('Employee', {'status': 'Active'})
+ employee = frappe.db.get_value('Employee', {'status': 'Active', 'company': '_Test Company'})
separation = frappe.new_doc('Employee Separation')
separation.employee = employee
+ separation.boarding_begins_on = getdate()
separation.company = '_Test Company'
separation.append('activities', {
'activity_name': 'Deactivate Employee',
diff --git a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js b/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js
deleted file mode 100644
index 66fd4508046..00000000000
--- a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Separation Template", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Separation Template
- () => frappe.tests.make('Employee Separation Template', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js
deleted file mode 100644
index 05a3e1a5738..00000000000
--- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Transfer", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Transfer
- () => frappe.tests.make('Employee Transfer', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js b/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js
deleted file mode 100644
index 00a334a63d7..00000000000
--- a/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Transfer Property", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Transfer Property
- () => frappe.tests.make('Employee Transfer Property', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/hr_settings/test_hr_settings.js b/erpnext/hr/doctype/hr_settings/test_hr_settings.js
deleted file mode 100644
index f32640ba5c6..00000000000
--- a/erpnext/hr/doctype/hr_settings/test_hr_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: HR Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new HR Settings
- () => frappe.tests.make('HR Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js b/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js
deleted file mode 100644
index 65879098e87..00000000000
--- a/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Identification Document Type", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Identification Document Type
- () => frappe.tests.make('Identification Document Type', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js b/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js
deleted file mode 100644
index c093928f321..00000000000
--- a/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Job Applicant Source", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Job Applicant Source
- () => frappe.tests.make('Job Applicant Source', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js
deleted file mode 100644
index cafd9602cbe..00000000000
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Leave Encashment", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Leave Encashment
- () => frappe.tests.make('Leave Encashment', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.js b/erpnext/hr/doctype/leave_period/test_leave_period.js
deleted file mode 100644
index ec0a8096890..00000000000
--- a/erpnext/hr/doctype/leave_period/test_leave_period.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Leave Period", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Leave Period
- () => frappe.tests.make('Leave Period', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.js b/erpnext/hr/doctype/leave_policy/test_leave_policy.js
deleted file mode 100644
index 5404a63bed1..00000000000
--- a/erpnext/hr/doctype/leave_policy/test_leave_policy.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Leave Policy", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Leave Policy
- () => frappe.tests.make('Leave Policy', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js b/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js
deleted file mode 100644
index 1c8995b7967..00000000000
--- a/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Leave Policy Detail", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Leave Policy Detail
- () => frappe.tests.make('Leave Policy Detail', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js b/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js
deleted file mode 100644
index 936c21ccf05..00000000000
--- a/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Purpose of Travel", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Purpose of Travel
- () => frappe.tests.make('Purpose of Travel', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js
deleted file mode 100644
index 77272877423..00000000000
--- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shift Assignment", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Shift Assignment
- () => frappe.tests.make('Shift Assignment', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.js b/erpnext/hr/doctype/shift_request/test_shift_request.js
deleted file mode 100644
index 9c8cd70020c..00000000000
--- a/erpnext/hr/doctype/shift_request/test_shift_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shift Request", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Shift Request
- () => frappe.tests.make('Shift Request', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.js b/erpnext/hr/doctype/shift_type/test_shift_type.js
deleted file mode 100644
index 846f9316f58..00000000000
--- a/erpnext/hr/doctype/shift_type/test_shift_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shift Type", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Shift Type
- () => frappe.tests.make('Shift Type', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js
deleted file mode 100644
index 64320bcd92b..00000000000
--- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Staffing Plan", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Staffing Plan
- () => frappe.tests.make('Staffing Plan', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/training_program/test_training_program.js b/erpnext/hr/doctype/training_program/test_training_program.js
deleted file mode 100644
index 3a62b2fa221..00000000000
--- a/erpnext/hr/doctype/training_program/test_training_program.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Training Program", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Training Program
- () => frappe.tests.make('Training Program', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/training_result/test_training_result.js b/erpnext/hr/doctype/training_result/test_training_result.js
deleted file mode 100644
index cb1d7fb27a3..00000000000
--- a/erpnext/hr/doctype/training_result/test_training_result.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Training Result", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Training Result
- () => frappe.tests.make('Training Result', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/travel_request/test_travel_request.js b/erpnext/hr/doctype/travel_request/test_travel_request.js
deleted file mode 100644
index 7e645918239..00000000000
--- a/erpnext/hr/doctype/travel_request/test_travel_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Travel Request", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Travel Request
- () => frappe.tests.make('Travel Request', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hr/doctype/vehicle/test_vehicle.js b/erpnext/hr/doctype/vehicle/test_vehicle.js
deleted file mode 100644
index 4d40cce086a..00000000000
--- a/erpnext/hr/doctype/vehicle/test_vehicle.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Vehicle", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Vehicle
- () => frappe.tests.make('Vehicle', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js b/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js
deleted file mode 100644
index fba3e098d4b..00000000000
--- a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Marketplace Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Marketplace Settings
- () => frappe.tests.make('Marketplace Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js
deleted file mode 100644
index e0f05b1abbd..00000000000
--- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Maintenance Schedule", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Maintenance Schedule
- () => frappe.tests.make('Maintenance Schedule', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
index 53ecdf5a61f..6b3f18484ad 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
@@ -73,12 +73,16 @@ erpnext.maintenance.MaintenanceVisit = class MaintenanceVisit extends frappe.ui.
if (this.frm.doc.docstatus === 0) {
this.frm.add_custom_button(__('Maintenance Schedule'),
function () {
+ if (!me.frm.doc.customer) {
+ frappe.msgprint(__('Please select Customer first'));
+ return;
+ }
erpnext.utils.map_current_doc({
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
source_doctype: "Maintenance Schedule",
target: me.frm,
setters: {
- customer: me.frm.doc.customer || undefined,
+ customer: me.frm.doc.customer,
},
get_query_filters: {
docstatus: 1,
@@ -104,12 +108,16 @@ erpnext.maintenance.MaintenanceVisit = class MaintenanceVisit extends frappe.ui.
}, __("Get Items From"));
this.frm.add_custom_button(__('Sales Order'),
function () {
+ if (!me.frm.doc.customer) {
+ frappe.msgprint(__('Please select Customer first'));
+ return;
+ }
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit",
source_doctype: "Sales Order",
target: me.frm,
setters: {
- customer: me.frm.doc.customer || undefined,
+ customer: me.frm.doc.customer,
},
get_query_filters: {
docstatus: 1,
diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js
deleted file mode 100644
index 51a0d948419..00000000000
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Blanket Order", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Blanket Order
- () => frappe.tests.make('Blanket Order', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index e72c8eb4088..05123d5f856 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -446,6 +446,11 @@ var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) {
},
callback: function(r) {
d = locals[cdt][cdn];
+ if (d.is_process_loss) {
+ r.message.rate = 0;
+ r.message.base_rate = 0;
+ }
+
$.extend(d, r.message);
refresh_field("items");
refresh_field("scrap_items");
@@ -655,3 +660,58 @@ frappe.ui.form.on("BOM", "with_operations", function(frm) {
frm.set_value("operations", []);
}
});
+
+frappe.tour['BOM'] = [
+ {
+ fieldname: "item",
+ title: "Item",
+ description: __("Select the Item to be manufactured. The Item name, UoM, Company, and Currency will be fetched automatically.")
+ },
+ {
+ fieldname: "quantity",
+ title: "Quantity",
+ description: __("Enter the quantity of the Item that will be manufactured from this Bill of Materials.")
+ },
+ {
+ fieldname: "with_operations",
+ title: "With Operations",
+ description: __("To add Operations tick the 'With Operations' checkbox.")
+ },
+ {
+ fieldname: "items",
+ title: "Raw Materials",
+ description: __("Select the raw materials (Items) required to manufacture the Item")
+ }
+];
+
+frappe.ui.form.on("BOM Scrap Item", {
+ item_code(frm, cdt, cdn) {
+ const { item_code } = locals[cdt][cdn];
+ if (item_code === frm.doc.item) {
+ locals[cdt][cdn].is_process_loss = 1;
+ trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code);
+ }
+ },
+});
+
+function trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) {
+ frappe.prompt(
+ {
+ fieldname: "percent",
+ fieldtype: "Percent",
+ label: __("% Finished Item Quantity"),
+ description:
+ __("Set quantity of process loss item:") +
+ ` ${item_code} ` +
+ __("as a percentage of finished item quantity"),
+ },
+ (data) => {
+ const row = locals[cdt][cdn];
+ row.stock_qty = (frm.doc.quantity * data.percent) / 100;
+ row.qty = row.stock_qty / (row.conversion_factor || 1);
+ refresh_field("scrap_items");
+ },
+ __("Set Process Loss Item Quantity"),
+ __("Set Quantity")
+ );
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 0ba85078ead..6e1c7dd81b1 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -148,6 +148,7 @@ class BOM(WebsiteGenerator):
self.set_plc_conversion_rate()
self.validate_uom_is_interger()
self.set_bom_material_details()
+ self.set_bom_scrap_items_detail()
self.validate_materials()
self.set_routing_operations()
self.validate_operations()
@@ -155,6 +156,7 @@ class BOM(WebsiteGenerator):
self.update_stock_qty()
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False)
self.set_bom_level()
+ self.validate_scrap_items()
def get_context(self, context):
context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
@@ -200,7 +202,7 @@ class BOM(WebsiteGenerator):
def set_bom_material_details(self):
for item in self.get("items"):
- self.validate_bom_currecny(item)
+ self.validate_bom_currency(item)
ret = self.get_bom_material_detail({
"company": self.company,
@@ -219,6 +221,19 @@ class BOM(WebsiteGenerator):
if not item.get(r):
item.set(r, ret[r])
+ def set_bom_scrap_items_detail(self):
+ for item in self.get("scrap_items"):
+ args = {
+ "item_code": item.item_code,
+ "company": self.company,
+ "scrap_items": True,
+ "bom_no": '',
+ }
+ ret = self.get_bom_material_detail(args)
+ for key, value in ret.items():
+ if item.get(key) is None:
+ item.set(key, value)
+
@frappe.whitelist()
def get_bom_material_detail(self, args=None):
""" Get raw material details like uom, desc and rate"""
@@ -255,7 +270,7 @@ class BOM(WebsiteGenerator):
return ret_item
- def validate_bom_currecny(self, item):
+ def validate_bom_currency(self, item):
if item.get('bom_no') and frappe.db.get_value('BOM', item.get('bom_no'), 'currency') != self.currency:
frappe.throw(_("Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2}")
.format(item.idx, item.bom_no, self.currency))
@@ -432,25 +447,29 @@ class BOM(WebsiteGenerator):
frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx))
check_list.append(m)
- def check_recursion(self, bom_list=[]):
+ def check_recursion(self, bom_list=None):
""" Check whether recursion occurs in any bom"""
+ def _throw_error(bom_name):
+ frappe.throw(_("BOM recursion: {0} cannot be parent or child of {0}").format(bom_name))
+
bom_list = self.traverse_tree()
- bom_nos = frappe.get_all('BOM Item', fields=["bom_no"],
- filters={'parent': ('in', bom_list), 'parenttype': 'BOM'})
+ child_items = frappe.get_all('BOM Item', fields=["bom_no", "item_code"],
+ filters={'parent': ('in', bom_list), 'parenttype': 'BOM'}) or []
- raise_exception = False
- if bom_nos and self.name in [d.bom_no for d in bom_nos]:
- raise_exception = True
+ child_bom = {d.bom_no for d in child_items}
+ child_items_codes = {d.item_code for d in child_items}
- if not raise_exception:
- bom_nos = frappe.get_all('BOM Item', fields=["parent"],
- filters={'bom_no': self.name, 'parenttype': 'BOM'})
+ if self.name in child_bom:
+ _throw_error(self.name)
- if self.name in [d.parent for d in bom_nos]:
- raise_exception = True
+ if self.item in child_items_codes:
+ _throw_error(self.item)
- if raise_exception:
- frappe.throw(_("BOM recursion: {0} cannot be parent or child of {1}").format(self.name, self.name))
+ bom_nos = frappe.get_all('BOM Item', fields=["parent"],
+ filters={'bom_no': self.name, 'parenttype': 'BOM'}) or []
+
+ if self.name in {d.parent for d in bom_nos}:
+ _throw_error(self.name)
def traverse_tree(self, bom_list=None):
def _get_children(bom_no):
@@ -498,17 +517,21 @@ class BOM(WebsiteGenerator):
def update_rate_and_time(self, row, update_hour_rate = False):
if not row.hour_rate or update_hour_rate:
hour_rate = flt(frappe.get_cached_value("Workstation", row.workstation, "hour_rate"))
- row.hour_rate = (hour_rate / flt(self.conversion_rate)
- if self.conversion_rate and hour_rate else hour_rate)
+
+ if hour_rate:
+ row.hour_rate = (hour_rate / flt(self.conversion_rate)
+ if self.conversion_rate and hour_rate else hour_rate)
if self.routing:
- row.time_in_mins = flt(frappe.db.get_value("BOM Operation", {
+ time_in_mins = flt(frappe.db.get_value("BOM Operation", {
"workstation": row.workstation,
"operation": row.operation,
- "sequence_id": row.sequence_id,
"parent": self.routing
}, ["time_in_mins"]))
+ if time_in_mins:
+ row.time_in_mins = time_in_mins
+
if row.hour_rate and row.time_in_mins:
row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate)
row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0
@@ -691,6 +714,32 @@ class BOM(WebsiteGenerator):
if update:
self.db_set("bom_level", self.bom_level)
+ def validate_scrap_items(self):
+ for item in self.scrap_items:
+ msg = ""
+ if item.item_code == self.item and not item.is_process_loss:
+ msg = _('Scrap/Loss Item: {0} should have Is Process Loss checked as it is the same as the item to be manufactured or repacked.') \
+ .format(frappe.bold(item.item_code))
+ elif item.item_code != self.item and item.is_process_loss:
+ msg = _('Scrap/Loss Item: {0} should not have Is Process Loss checked as it is different from the item to be manufactured or repacked') \
+ .format(frappe.bold(item.item_code))
+
+ must_be_whole_number = frappe.get_value("UOM", item.stock_uom, "must_be_whole_number")
+ if item.is_process_loss and must_be_whole_number:
+ msg = _("Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item as {1} is a whole UOM.") \
+ .format(frappe.bold(item.item_code), frappe.bold(item.stock_uom))
+
+ if item.is_process_loss and (item.stock_qty >= self.quantity):
+ msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity.") \
+ .format(frappe.bold(item.item_code))
+
+ if item.is_process_loss and (item.rate > 0):
+ msg = _("Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked.") \
+ .format(frappe.bold(item.item_code))
+
+ if msg:
+ frappe.throw(msg, title=_("Note"))
+
def get_bom_item_rate(args, bom_doc):
if bom_doc.rm_cost_as_per == 'Valuation Rate':
rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1)
@@ -808,8 +857,11 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
elif fetch_scrap_items:
- query = query.format(table="BOM Scrap Item", where_conditions="",
- select_columns=", bom_item.idx, item.description", is_stock_item=is_stock_item, qty_field="stock_qty")
+ query = query.format(
+ table="BOM Scrap Item", where_conditions="",
+ select_columns=", bom_item.idx, item.description, is_process_loss",
+ is_stock_item=is_stock_item, qty_field="stock_qty"
+ )
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
else:
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index c89f7d66fdf..8408f10b187 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -280,13 +280,82 @@ class TestBOM(unittest.TestCase):
self.assertEqual(reqd_item.qty, created_item.qty)
self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty)
+ def test_bom_recursion_1st_level(self):
+ """BOM should not allow BOM item again in child"""
+ item_code = "_Test BOM Recursion"
+ make_item(item_code, {'is_stock_item': 1})
+
+ bom = frappe.new_doc("BOM")
+ bom.item = item_code
+ bom.append("items", frappe._dict(item_code=item_code))
+ with self.assertRaises(frappe.ValidationError) as err:
+ bom.save()
+
+ self.assertTrue("recursion" in str(err.exception).lower())
+ frappe.delete_doc("BOM", bom.name, ignore_missing=True)
+
+ def test_bom_recursion_transitive(self):
+ item1 = "_Test BOM Recursion"
+ item2 = "_Test BOM Recursion 2"
+ make_item(item1, {'is_stock_item': 1})
+ make_item(item2, {'is_stock_item': 1})
+
+ bom1 = frappe.new_doc("BOM")
+ bom1.item = item1
+ bom1.append("items", frappe._dict(item_code=item2))
+ bom1.save()
+ bom1.submit()
+
+ bom2 = frappe.new_doc("BOM")
+ bom2.item = item2
+ bom2.append("items", frappe._dict(item_code=item1))
+
+ with self.assertRaises(frappe.ValidationError) as err:
+ bom2.save()
+ bom2.submit()
+
+ self.assertTrue("recursion" in str(err.exception).lower())
+
+ bom1.cancel()
+ frappe.delete_doc("BOM", bom1.name, ignore_missing=True, force=True)
+ frappe.delete_doc("BOM", bom2.name, ignore_missing=True, force=True)
+
+ def test_bom_with_process_loss_item(self):
+ fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items()
+
+ if not frappe.db.exists("BOM", f"BOM-{fg_item_non_whole.item_code}-001"):
+ bom_doc = create_bom_with_process_loss_item(
+ fg_item_non_whole, bom_item, scrap_qty=0.25, scrap_rate=0, fg_qty=1
+ )
+ bom_doc.submit()
+
+ bom_doc = create_bom_with_process_loss_item(
+ fg_item_non_whole, bom_item, scrap_qty=2, scrap_rate=0
+ )
+ # PL Item qty can't be >= FG Item qty
+ self.assertRaises(frappe.ValidationError, bom_doc.submit)
+
+ bom_doc = create_bom_with_process_loss_item(
+ fg_item_non_whole, bom_item, scrap_qty=1, scrap_rate=100
+ )
+ # PL Item rate has to be 0
+ self.assertRaises(frappe.ValidationError, bom_doc.submit)
+
+ bom_doc = create_bom_with_process_loss_item(
+ fg_item_whole, bom_item, scrap_qty=0.25, scrap_rate=0
+ )
+ # Items with whole UOMs can't be PL Items
+ self.assertRaises(frappe.ValidationError, bom_doc.submit)
+
+ bom_doc = create_bom_with_process_loss_item(
+ fg_item_non_whole, bom_item, scrap_qty=0.25, scrap_rate=0, is_process_loss=0
+ )
+ # FG Items in Scrap/Loss Table should have Is Process Loss set
+ self.assertRaises(frappe.ValidationError, bom_doc.submit)
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
-
-
-
def level_order_traversal(node):
traversal = []
q = deque()
@@ -332,6 +401,7 @@ def create_nested_bom(tree, prefix="_Test bom "):
bom = frappe.get_doc(doctype="BOM", item=bom_item_code)
for child_item in child_items.keys():
bom.append("items", {"item_code": prefix + child_item})
+ bom.currency = "INR"
bom.insert()
bom.submit()
@@ -353,3 +423,45 @@ def reset_item_valuation_rate(item_code, warehouse_list=None, qty=None, rate=Non
for warehouse in warehouse_list:
create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=qty, rate=rate)
+
+def create_bom_with_process_loss_item(
+ fg_item, bom_item, scrap_qty, scrap_rate, fg_qty=2, is_process_loss=1):
+ bom_doc = frappe.new_doc("BOM")
+ bom_doc.item = fg_item.item_code
+ bom_doc.quantity = fg_qty
+ bom_doc.append("items", {
+ "item_code": bom_item.item_code,
+ "qty": 1,
+ "uom": bom_item.stock_uom,
+ "stock_uom": bom_item.stock_uom,
+ "rate": 100.0
+ })
+ bom_doc.append("scrap_items", {
+ "item_code": fg_item.item_code,
+ "qty": scrap_qty,
+ "stock_qty": scrap_qty,
+ "uom": fg_item.stock_uom,
+ "stock_uom": fg_item.stock_uom,
+ "rate": scrap_rate,
+ "is_process_loss": is_process_loss
+ })
+ bom_doc.currency = "INR"
+ return bom_doc
+
+def create_process_loss_bom_items():
+ item_list = [
+ ("_Test Item - Non Whole UOM", "Kg"),
+ ("_Test Item - Whole UOM", "Unit"),
+ ("_Test PL BOM Item", "Unit")
+ ]
+ return [create_process_loss_bom_item(it) for it in item_list]
+
+def create_process_loss_bom_item(item_tuple):
+ item_code, stock_uom = item_tuple
+ if frappe.db.exists("Item", item_code) is None:
+ return make_item(
+ item_code,
+ {'stock_uom':stock_uom, 'valuation_rate':100}
+ )
+ else:
+ return frappe.get_doc("Item", item_code)
diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json
index 9f7091dd8d7..7018082e402 100644
--- a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json
+++ b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json
@@ -1,345 +1,112 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-09-26 02:19:21.642081",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-09-26 02:19:21.642081",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item_code",
+ "column_break_2",
+ "item_name",
+ "is_process_loss",
+ "quantity_and_rate",
+ "stock_qty",
+ "rate",
+ "amount",
+ "column_break_6",
+ "stock_uom",
+ "base_rate",
+ "base_amount"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_code",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "options": "Item",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Item Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "quantity_and_rate",
- "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": "Quantity and Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "quantity_and_rate",
+ "fieldtype": "Section Break",
+ "label": "Quantity and Rate"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "stock_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "stock_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Qty",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 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": 1,
- "in_standard_filter": 0,
- "label": "Rate",
- "length": 0,
- "no_copy": 0,
- "options": "currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Rate",
+ "options": "currency"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amount",
- "length": 0,
- "no_copy": 0,
- "options": "currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "label": "Amount",
+ "options": "currency",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_6",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "stock_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": "Stock UOM",
- "length": 0,
- "no_copy": 0,
- "options": "UOM",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "label": "Stock UOM",
+ "options": "UOM",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "base_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": "Basic Rate (Company Currency)",
- "length": 0,
- "no_copy": 0,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "base_rate",
+ "fieldtype": "Currency",
+ "label": "Basic Rate (Company Currency)",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "base_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Basic Amount (Company Currency)",
- "length": 0,
- "no_copy": 0,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "base_amount",
+ "fieldtype": "Currency",
+ "label": "Basic Amount (Company Currency)",
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_process_loss",
+ "fieldtype": "Check",
+ "label": "Is Process Loss"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-07-04 16:04:32.442287",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "BOM Scrap Item",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-06-22 16:46:12.153311",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "BOM Scrap Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js
deleted file mode 100644
index d220df2824f..00000000000
--- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: BOM Update Tool", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially('BOM Update Tool', [
- // insert a new BOM Update Tool
- () => frappe.tests.make([
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 66e2394b847..3efbe88adaf 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -26,17 +26,17 @@ class JobCard(Document):
self.set_status()
self.validate_operation_id()
self.validate_sequence_id()
- self.get_sub_operations()
+ self.set_sub_operations()
self.update_sub_operation_status()
- def get_sub_operations(self):
+ def set_sub_operations(self):
if self.operation:
self.sub_operations = []
- for row in frappe.get_all("Sub Operation",
- filters = {"parent": self.operation}, fields=["operation", "idx"]):
- row.status = "Pending"
+ for row in frappe.get_all('Sub Operation',
+ filters = {'parent': self.operation}, fields=['operation', 'idx'], order_by='idx'):
+ row.status = 'Pending'
row.sub_operation = row.operation
- self.append("sub_operations", row)
+ self.append('sub_operations', row)
def validate_time_logs(self):
self.total_time_in_mins = 0.0
@@ -690,7 +690,7 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta
target.set('time_logs', [])
target.set('employee', [])
target.set('items', [])
- target.get_sub_operations()
+ target.set_sub_operations()
target.get_required_items()
target.validate_time_logs()
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.js b/erpnext/manufacturing/doctype/job_card/test_job_card.js
deleted file mode 100644
index 5dc7805d226..00000000000
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Job Card", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Job Card
- () => frappe.tests.make('Job Card', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js b/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js
deleted file mode 100644
index 2b2589eddd8..00000000000
--- a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Manufacturing Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially('Manufacturing Settings', [
- // insert a new Manufacturing Settings
- () => frappe.tests.make([
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js b/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js
deleted file mode 100644
index 14c6e393847..00000000000
--- a/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Material Request Plan Item", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Material Request Plan Item
- () => frappe.tests.make('Material Request Plan Item', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/manufacturing/doctype/operation/operation.js b/erpnext/manufacturing/doctype/operation/operation.js
index 2936e33b118..95643bf49fc 100644
--- a/erpnext/manufacturing/doctype/operation/operation.js
+++ b/erpnext/manufacturing/doctype/operation/operation.js
@@ -12,3 +12,21 @@ frappe.ui.form.on('Operation', {
});
}
});
+
+frappe.tour['Operation'] = [
+ {
+ fieldname: "__newname",
+ title: "Operation Name",
+ description: __("Enter a name for the Operation, for example, Cutting.")
+ },
+ {
+ fieldname: "workstation",
+ title: "Default Workstation",
+ description: __("Select the Default Workstation where the Operation will be performed. This will be fetched in BOMs and Work Orders.")
+ },
+ {
+ fieldname: "sub_operations",
+ title: "Sub Operations",
+ description: __("If an operation is divided into sub operations, they can be added here.")
+ }
+];
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index d198a6962a5..847004fc47f 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -428,3 +428,36 @@ cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = fu
]
}
};
+
+frappe.tour['Production Plan'] = [
+ {
+ fieldname: "get_items_from",
+ title: "Get Items From",
+ description: __("Select whether to get items from a Sales Order or a Material Request. For now select Sales Order.\n A Production Plan can also be created manually where you can select the Items to manufacture.")
+ },
+ {
+ fieldname: "get_sales_orders",
+ title: "Get Sales Orders",
+ description: __("Click on Get Sales Orders to fetch sales orders based on the above filters.")
+ },
+ {
+ fieldname: "get_items",
+ title: "Get Finished Goods for Manufacture",
+ description: __("Click on 'Get Finished Goods for Manufacture' to fetch the items from the above Sales Orders. Items only for which a BOM is present will be fetched.")
+ },
+ {
+ fieldname: "po_items",
+ title: "Finished Goods",
+ description: __("On expanding a row in the Items to Manufacture table, you'll see an option to 'Include Exploded Items'. Ticking this includes raw materials of the sub-assembly items in the production process.")
+ },
+ {
+ fieldname: "include_non_stock_items",
+ title: "Include Non Stock Items",
+ description: __("To include non-stock items in the material request planning. i.e. Items for which 'Maintain Stock' checkbox is unticked.")
+ },
+ {
+ fieldname: "include_subcontracted_items",
+ title: "Include Subcontracted Items",
+ description: __("To add subcontracted Item's raw materials if include exploded items is disabled.")
+ }
+];
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index b4c663507ce..6b61c6d330f 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -213,7 +213,6 @@ class ProductionPlan(Document):
})
pi = self.append('po_items', {
- 'include_exploded_items': 1,
'warehouse': data.warehouse,
'item_code': data.item_code,
'description': data.description or item_details.description,
@@ -224,6 +223,7 @@ class ProductionPlan(Document):
'planned_start_date': now_datetime(),
'product_bundle_item': data.parent_item
})
+ pi._set_defaults()
if self.get_items_from == "Sales Order":
pi.sales_order = data.parent
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_list.js b/erpnext/manufacturing/doctype/production_plan/production_plan_list.js
index c2e3e6d7124..8f946866247 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan_list.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan_list.js
@@ -1,4 +1,5 @@
frappe.listview_settings['Production Plan'] = {
+ hide_name_column: true,
add_fields: ["status"],
filters: [["status", "!=", "Closed"]],
get_indicator: function (doc) {
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.js b/erpnext/manufacturing/doctype/production_plan/test_production_plan.js
deleted file mode 100644
index ef7d64c92da..00000000000
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Production Plan", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Production Plan
- () => frappe.tests.make('Production Plan', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index a5b9ff845fc..78028039c48 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -288,6 +288,7 @@ class TestProductionPlan(unittest.TestCase):
self.assertEqual(warehouses, expected_warehouses)
def test_get_sales_order_with_variant(self):
+ rm_item = create_item('PIV_RM', valuation_rate = 100)
if not frappe.db.exists('Item', {"item_code": 'PIV'}):
item = create_item('PIV', valuation_rate = 100)
variant_settings = {
@@ -300,20 +301,20 @@ class TestProductionPlan(unittest.TestCase):
}
item.update(variant_settings)
item.save()
- parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV'])
+ parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code])
if not frappe.db.exists('BOM', {"item": 'PIV'}):
- parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV'])
+ parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code])
else:
parent_bom = frappe.get_doc('BOM', {"item": 'PIV'})
if not frappe.db.exists('Item', {"item_code": 'PIV-RED'}):
variant = create_variant("PIV", {"Colour": "Red"})
variant.save()
- variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code])
+ variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code])
else:
variant = frappe.get_doc('Item', 'PIV-RED')
if not frappe.db.exists('BOM', {"item": 'PIV-RED'}):
- variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code])
+ variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code])
"""Testing when item variant has a BOM"""
so = make_sales_order(item_code="PIV-RED", qty=5)
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
index 032c9cd9a21..c17a8e959db 100644
--- a/erpnext/manufacturing/doctype/routing/routing.js
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -69,3 +69,17 @@ frappe.ui.form.on('BOM Operation', {
frm.events.calculate_operating_cost(frm, d);
}
});
+
+frappe.tour['Routing'] = [
+ {
+ fieldname: "routing_name",
+ title: "Routing Name",
+ description: __("Enter a name for Routing.")
+ },
+ {
+ fieldname: "operations",
+ title: "BOM Operations",
+ description: __("Enter the Operation, the table will fetch the Operation details like Hourly Rate, Workstation automatically.\n\n After that, set the Operation Time in minutes and the table will calculate the Operation Costs based on the Hourly Rate and Operation Time.")
+ }
+];
+
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.js b/erpnext/manufacturing/doctype/routing/test_routing.js
deleted file mode 100644
index 6cb65494af2..00000000000
--- a/erpnext/manufacturing/doctype/routing/test_routing.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Routing", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Routing
- () => frappe.tests.make('Routing', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index bf1ccb71594..c0ed6115340 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -675,13 +675,18 @@ class TestWorkOrder(unittest.TestCase):
def test_valuation_rate_missing_on_make_stock_entry(self):
item_name = 'Test Valuation Rate Missing'
+ rm_item = '_Test raw material item'
make_item(item_name, {
"is_stock_item": 1,
"include_item_in_manufacturing": 1,
})
+ make_item('_Test raw material item', {
+ "is_stock_item": 1,
+ "include_item_in_manufacturing": 1,
+ })
if not frappe.db.get_value('BOM', {'item': item_name}):
- make_bom(item=item_name, raw_materials=[item_name], rm_qty=1)
+ make_bom(item=item_name, raw_materials=[rm_item], rm_qty=1)
company = "_Test Company with perpetual inventory"
source_warehouse = create_warehouse("Test Valuation Rate Missing Warehouse", company=company)
@@ -690,6 +695,71 @@ class TestWorkOrder(unittest.TestCase):
self.assertRaises(frappe.ValidationError, make_stock_entry, wo.name, 'Material Transfer for Manufacture')
+ def test_wo_completion_with_pl_bom(self):
+ from erpnext.manufacturing.doctype.bom.test_bom import create_process_loss_bom_items
+ from erpnext.manufacturing.doctype.bom.test_bom import create_bom_with_process_loss_item
+
+ qty = 4
+ scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG
+ source_warehouse = "Stores - _TC"
+ wip_warehouse = "_Test Warehouse - _TC"
+ fg_item_non_whole, _, bom_item = create_process_loss_bom_items()
+
+ test_stock_entry.make_stock_entry(item_code=bom_item.item_code,
+ target=source_warehouse, qty=4, basic_rate=100)
+
+ bom_no = f"BOM-{fg_item_non_whole.item_code}-001"
+ if not frappe.db.exists("BOM", bom_no):
+ bom_doc = create_bom_with_process_loss_item(
+ fg_item_non_whole, bom_item, scrap_qty=scrap_qty,
+ scrap_rate=0, fg_qty=1, is_process_loss=1
+ )
+ bom_doc.submit()
+
+ wo = make_wo_order_test_record(
+ production_item=fg_item_non_whole.item_code,
+ bom_no=bom_no,
+ wip_warehouse=wip_warehouse,
+ qty=qty,
+ skip_transfer=1,
+ stock_uom=fg_item_non_whole.stock_uom,
+ )
+
+ se = frappe.get_doc(
+ make_stock_entry(wo.name, "Material Transfer for Manufacture", qty)
+ )
+ se.get("items")[0].s_warehouse = "Stores - _TC"
+ se.insert()
+ se.submit()
+
+ se = frappe.get_doc(
+ make_stock_entry(wo.name, "Manufacture", qty)
+ )
+ se.insert()
+ se.submit()
+
+ # Testing stock entry values
+ items = se.get("items")
+ self.assertEqual(len(items), 3, "There should be 3 items including process loss.")
+
+ source_item, fg_item, pl_item = items
+
+ total_pl_qty = qty * scrap_qty
+ actual_fg_qty = qty - total_pl_qty
+
+ self.assertEqual(pl_item.qty, total_pl_qty)
+ self.assertEqual(fg_item.qty, actual_fg_qty)
+
+ # Testing Work Order values
+ self.assertEqual(
+ frappe.db.get_value("Work Order", wo.name, "produced_qty"),
+ qty
+ )
+ self.assertEqual(
+ frappe.db.get_value("Work Order", wo.name, "process_loss_qty"),
+ total_pl_qty
+ )
+
def get_scrap_item_details(bom_no):
scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 512048512ed..51c46f63ab7 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -731,3 +731,63 @@ erpnext.work_order = {
});
}
};
+
+frappe.tour['Work Order'] = [
+ {
+ fieldname: "production_item",
+ title: "Item to Manufacture",
+ description: __("Select the Item to be manufactured.")
+ },
+ {
+ fieldname: "bom_no",
+ title: "BOM No",
+ description: __("The default BOM for that item will be fetched by the system. You can also change the BOM.")
+ },
+ {
+ fieldname: "qty",
+ title: "Qty to Manufacture",
+ description: __("Enter the quantity to manufacture. Raw material Items will be fetched only when this is set.")
+ },
+ {
+ fieldname: "use_multi_level_bom",
+ title: "Use Multi-Level BOM",
+ description: __("This is enabled by default. If you want to plan materials for sub-assemblies of the Item you're manufacturing leave this enabled. If you plan and manufacture the sub-assemblies separately, you can disable this checkbox.")
+ },
+ {
+ fieldname: "source_warehouse",
+ title: "Source Warehouse",
+ description: __("The warehouse where you store your raw materials. Each required item can have a separate source warehouse. Group warehouse also can be selected as source warehouse. On submission of the Work Order, the raw materials will be reserved in these warehouses for production usage.")
+ },
+ {
+ fieldname: "fg_warehouse",
+ title: "Target Warehouse",
+ description: __("The warehouse where you store finished Items before they are shipped.")
+ },
+ {
+ fieldname: "wip_warehouse",
+ title: "Work-in-Progress Warehouse",
+ description: __("The warehouse where your Items will be transferred when you begin production. Group Warehouse can also be selected as a Work in Progress warehouse.")
+ },
+ {
+ fieldname: "scrap_warehouse",
+ title: "Scrap Warehouse",
+ description: __("If the BOM results in Scrap material, the Scrap Warehouse needs to be selected.")
+ },
+ {
+ fieldname: "required_items",
+ title: "Required Items",
+ description: __("All the required items (raw materials) will be fetched from BOM and populated in this table. Here you can also change the Source Warehouse for any item. And during the production, you can track transferred raw materials from this table.")
+ },
+ {
+ fieldname: "planned_start_date",
+ title: "Planned Start Date",
+ description: __("Set the Planned Start Date (an Estimated Date at which you want the Production to begin)")
+ },
+ {
+ fieldname: "operations",
+ title: "Operations",
+ description: __("If the selected BOM has Operations mentioned in it, the system will fetch all Operations from BOM, these values can be changed.")
+ },
+
+
+];
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 3b56854aaf3..913fc85af61 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -19,6 +19,7 @@
"qty",
"material_transferred_for_manufacturing",
"produced_qty",
+ "process_loss_qty",
"sales_order",
"project",
"serial_no_and_batch_for_finished_good_section",
@@ -64,16 +65,12 @@
"description",
"stock_uom",
"column_break2",
- "references_section",
"material_request",
"material_request_item",
"sales_order_item",
- "column_break_61",
"production_plan",
"production_plan_item",
"production_plan_sub_assembly_item",
- "parent_work_order",
- "bom_level",
"product_bundle_item",
"amended_from"
],
@@ -553,20 +550,29 @@
"read_only": 1
},
{
- "fieldname": "production_plan_sub_assembly_item",
- "fieldtype": "Data",
- "label": "Production Plan Sub-assembly Item",
- "no_copy": 1,
- "print_hide": 1,
- "read_only": 1
- }
+ "fieldname": "production_plan_sub_assembly_item",
+ "fieldtype": "Data",
+ "label": "Production Plan Sub-assembly Item",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval: doc.process_loss_qty",
+ "fieldname": "process_loss_qty",
+ "fieldtype": "Float",
+ "label": "Process Loss Qty",
+ "no_copy": 1,
+ "non_negative": 1,
+ "read_only": 1
+ }
],
"icon": "fa fa-cogs",
"idx": 1,
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2021-06-28 16:19:14.902699",
+ "modified": "2021-08-24 15:14:03.844937",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 5fe9fec2af1..24b33d523e4 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -214,6 +214,7 @@ class WorkOrder(Document):
self.meta.get_label(fieldname), qty, completed_qty, self.name), StockOverProductionError)
self.db_set(fieldname, qty)
+ self.set_process_loss_qty()
from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item
@@ -223,6 +224,22 @@ class WorkOrder(Document):
if self.production_plan:
self.update_production_plan_status()
+ def set_process_loss_qty(self):
+ process_loss_qty = flt(frappe.db.sql("""
+ SELECT sum(qty) FROM `tabStock Entry Detail`
+ WHERE
+ is_process_loss=1
+ AND parent IN (
+ SELECT name FROM `tabStock Entry`
+ WHERE
+ work_order=%s
+ AND purpose='Manufacture'
+ AND docstatus=1
+ )
+ """, (self.name, ))[0][0])
+ if process_loss_qty is not None:
+ self.db_set('process_loss_qty', process_loss_qty)
+
def update_production_plan_status(self):
production_plan = frappe.get_doc('Production Plan', self.production_plan)
produced_qty = 0
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js
index d8d25fc6f83..4f5231e449c 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.js
+++ b/erpnext/manufacturing/doctype/workstation/workstation.js
@@ -16,4 +16,29 @@ frappe.ui.form.on("Workstation", {
})
}
}
-})
+});
+
+frappe.tour['Workstation'] = [
+ {
+ fieldname: "workstation_name",
+ title: "Workstation Name",
+ description: __("You can set it as a machine name or operation type. For example, stiching machine 12")
+ },
+ {
+ fieldname: "production_capacity",
+ title: "Production Capacity",
+ description: __("No. of parallel job cards which can be allowed on this workstation. Example: 2 would mean this workstation can process production for two Work Orders at a time.")
+ },
+ {
+ fieldname: "holiday_list",
+ title: "Holiday List",
+ description: __("A Holiday List can be added to exclude counting these days for the Workstation.")
+ },
+ {
+ fieldname: "working_hours",
+ title: "Working Hours",
+ description: __("Under Working Hours table, you can add start and end times for a Workstation. For example, a Workstation may be active from 9 am to 1 pm, then 2 pm to 5 pm. You can also specify the working hours based on shifts. While scheduling a Work Order, the system will check for the availability of the Workstation based on the working hours specified.")
+ },
+
+
+];
\ No newline at end of file
diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
index 7317152565a..032091f67e2 100644
--- a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
@@ -19,14 +19,14 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing",
"idx": 0,
"is_complete": 0,
- "modified": "2020-06-29 20:25:36.899106",
+ "modified": "2021-08-13 16:04:34.333812",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing",
"owner": "Administrator",
"steps": [
{
- "step": "Warehouse"
+ "step": "Explore Manufacturing Settings"
},
{
"step": "Workstation"
@@ -35,22 +35,22 @@
"step": "Operation"
},
{
- "step": "Create Product"
+ "step": "Routing"
},
{
- "step": "Create Raw Materials"
+ "step": "Create Product"
},
{
"step": "Create BOM"
},
{
- "step": "Work Order"
+ "step": "Production Planning"
},
{
- "step": "Explore Manufacturing Settings"
+ "step": "Work Order"
}
],
"subtitle": "Products, Raw Materials, BOM, Work Order, and more.",
"success_message": "Manufacturing module is all set up!",
"title": "Let's Set Up the Manufacturing Module."
-}
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
index 84b4088f233..9d7a58c1214 100644
--- a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
+++ b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
@@ -1,19 +1,21 @@
{
"action": "Create Entry",
+ "action_label": "Create your first Bill of Materials",
"creation": "2020-05-05 16:41:20.239696",
+ "description": "# Create a Bill of Materials\n\nA Bill of Materials (BOM) is a list of items and sub-assemblies with quantities required to manufacture an Item.\n\nBOM also provides cost estimation for the production of the item. It takes raw-materials cost based on valuation and operations to cost based on routing, which gives total costing for a BOM.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 1,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-19 12:51:31.315686",
+ "modified": "2021-08-13 16:00:51.092671",
"modified_by": "Administrator",
"name": "Create BOM",
"owner": "Administrator",
"reference_document": "BOM",
+ "show_form_tour": 1,
"show_full_form": 1,
- "title": "Create a BOM (Bill of Material)",
+ "title": "Bill of Materials",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_product/create_product.json b/erpnext/manufacturing/onboarding_step/create_product/create_product.json
index 0ffa30158b0..ad7345234d1 100644
--- a/erpnext/manufacturing/onboarding_step/create_product/create_product.json
+++ b/erpnext/manufacturing/onboarding_step/create_product/create_product.json
@@ -1,19 +1,21 @@
{
"action": "Create Entry",
+ "action_label": "Create an Item",
"creation": "2020-05-05 16:42:31.476275",
+ "description": "# Create Items for Bill of Materials\n\nOne of the prerequisites of a BOM is the creation of raw materials, sub-assembly, and finished items. Once these items are created, you will be able to proceed to the Bill of Materials master, which is composed of items and routing.\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 1,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-19 12:50:59.010439",
+ "modified": "2021-08-13 16:00:22.407811",
"modified_by": "Administrator",
"name": "Create Product",
"owner": "Administrator",
"reference_document": "Item",
- "show_full_form": 0,
- "title": "Create a Finished Good",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Finished Items",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
index 0764f2e44a8..3f94764f089 100644
--- a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
+++ b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
@@ -5,7 +5,6 @@
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-19 11:53:25.147837",
@@ -13,6 +12,7 @@
"name": "Create Raw Materials",
"owner": "Administrator",
"reference_document": "Item",
+ "show_form_tour": 0,
"show_full_form": 0,
"title": "Create Raw Materials",
"validate_action": 1
diff --git a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
index 7ef202ee4e7..1d2c27e3796 100644
--- a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
+++ b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
@@ -1,20 +1,22 @@
{
"action": "Show Form Tour",
+ "action_label": "Take a walk-through of Manufacturing Settings",
"creation": "2020-05-19 11:55:11.378374",
+ "description": "# Review Manufacturing Settings\n\nIn ERPNext, the Manufacturing module\u2019s features are configurable as per your business needs. Manufacturing Settings is the place where you can set your preferences for:\n\n- Capacity planning for allocating jobs to workstations\n- Raw-material consumption based on BOM or actual\n- Default values and over-production allowance\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 1,
"is_skipped": 0,
- "modified": "2020-05-26 20:28:03.558199",
+ "modified": "2021-08-13 15:59:32.145655",
"modified_by": "Administrator",
"name": "Explore Manufacturing Settings",
"owner": "Administrator",
"reference_document": "Manufacturing Settings",
+ "show_form_tour": 0,
"show_full_form": 0,
- "title": "Explore Manufacturing Settings",
+ "title": "Manufacturing Settings",
"validate_action": 0,
"video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4"
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/operation/operation.json b/erpnext/manufacturing/onboarding_step/operation/operation.json
index b532e6778c4..2e9592185ef 100644
--- a/erpnext/manufacturing/onboarding_step/operation/operation.json
+++ b/erpnext/manufacturing/onboarding_step/operation/operation.json
@@ -1,19 +1,21 @@
{
"action": "Create Entry",
+ "action_label": "Let\u2019s create an Operation",
"creation": "2020-05-12 16:15:31.706756",
+ "description": "# Create Operations\n\nAn Operation refers to any manufacturing operation performed on the raw materials to process it further in the manufacturing path. As an example, if you are into garments manufacturing, you will create Operations like fabric cutting, stitching, and washing as some of the operations.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-19 12:50:41.642754",
+ "modified": "2021-08-13 15:59:53.313532",
"modified_by": "Administrator",
"name": "Operation",
"owner": "Administrator",
"reference_document": "Operation",
- "show_full_form": 0,
- "title": "Create a Operation",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Operation",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json b/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json
new file mode 100644
index 00000000000..ff9fdb9fd0c
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json
@@ -0,0 +1,21 @@
+{
+ "action": "Create Entry",
+ "action_label": "Learn more about Production Planning",
+ "creation": "2021-08-04 17:33:06.551077",
+ "description": "# How Production Planning Works\n\nProduction Plan helps in production and material planning for the Items planned for manufacturing. These production items can be committed via Sales Order (to Customers) or Material Requests (internally).\n",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-13 16:04:15.491414",
+ "modified_by": "Administrator",
+ "name": "Production Planning",
+ "owner": "Administrator",
+ "reference_document": "Production Plan",
+ "show_form_tour": 0,
+ "show_full_form": 1,
+ "title": "Production Planning",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/routing/routing.json b/erpnext/manufacturing/onboarding_step/routing/routing.json
new file mode 100644
index 00000000000..be4fbff171f
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/routing/routing.json
@@ -0,0 +1,21 @@
+{
+ "action": "Create Entry",
+ "action_label": "Check help to setup Routing",
+ "creation": "2021-08-04 11:56:42.455758",
+ "description": "# Setup Routing\n\nA Routing stores all Operations along with the description, hourly rate, operation time, batch size, etc. Click below to learn how the Routing template can be created, for quick selection in the BOM.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-13 16:00:07.391563",
+ "modified_by": "Administrator",
+ "name": "Routing",
+ "owner": "Administrator",
+ "reference_document": "Routing",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Routing",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/work_order/work_order.json b/erpnext/manufacturing/onboarding_step/work_order/work_order.json
index c63363e7cb2..d2756c92758 100644
--- a/erpnext/manufacturing/onboarding_step/work_order/work_order.json
+++ b/erpnext/manufacturing/onboarding_step/work_order/work_order.json
@@ -1,19 +1,21 @@
{
"action": "Create Entry",
+ "action_label": "Create your first Work Order",
"creation": "2020-05-12 16:15:56.084682",
+ "description": "# Create a Work Order\n\nA Work Order or a Job order is given to the manufacturing shop floor by the Production Manager to initiate the manufacturing of a certain quantity of an item. Work Order carriers details of production Item, its BOM, quantities to be manufactured, and operations.\n\nThrough Work Order, you can track various production status like:\n\n- Issue of raw-material to shop material\n- Progress on each Workstation via Job Card\n- Manufactured Quantity against Work Order\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-19 12:51:38.133150",
+ "modified": "2021-08-13 16:02:04.223536",
"modified_by": "Administrator",
"name": "Work Order",
"owner": "Administrator",
"reference_document": "Work Order",
+ "show_form_tour": 1,
"show_full_form": 1,
- "title": "Create a Work Order",
+ "title": "Work Order",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/workstation/workstation.json b/erpnext/manufacturing/onboarding_step/workstation/workstation.json
index df244bb494a..67dd52eba2c 100644
--- a/erpnext/manufacturing/onboarding_step/workstation/workstation.json
+++ b/erpnext/manufacturing/onboarding_step/workstation/workstation.json
@@ -1,19 +1,21 @@
{
"action": "Create Entry",
+ "action_label": "Let\u2019s create a Workstation",
"creation": "2020-05-12 16:14:14.930214",
+ "description": "# Create Workstations\n\nA Workstation stores information regarding the place where the workstation operations are performed. As an example, if you have ten sewing machines doing stitching jobs, each machine will be added as a workstation.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-19 12:50:33.938176",
+ "modified": "2021-08-13 15:59:59.634802",
"modified_by": "Administrator",
"name": "Workstation",
"owner": "Administrator",
"reference_document": "Workstation",
- "show_full_form": 0,
- "title": "Create a Workstation / Machine",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Workstation",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/certification_application/test_certification_application.js b/erpnext/non_profit/doctype/certification_application/test_certification_application.js
deleted file mode 100644
index 40e94864d49..00000000000
--- a/erpnext/non_profit/doctype/certification_application/test_certification_application.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Certification Application", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Certification Application
- () => frappe.tests.make('Certification Application', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js b/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js
deleted file mode 100644
index f6a72a43271..00000000000
--- a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Certified Consultant", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Certified Consultant
- () => frappe.tests.make('Certified Consultant', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/non_profit/doctype/chapter/test_chapter.js b/erpnext/non_profit/doctype/chapter/test_chapter.js
deleted file mode 100644
index e30d6a5bf9b..00000000000
--- a/erpnext/non_profit/doctype/chapter/test_chapter.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Chapter", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Chapter
- () => frappe.tests.make('Chapter', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/non_profit/doctype/donor_type/test_donor_type.js b/erpnext/non_profit/doctype/donor_type/test_donor_type.js
deleted file mode 100644
index 22dc18ed76a..00000000000
--- a/erpnext/non_profit/doctype/donor_type/test_donor_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Donor Type", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Member
- () => frappe.tests.make('Donor Type', [
- // values to be set
- {donor_type: 'Test Organization'},
- ]),
- () => {
- assert.equal(cur_frm.doc.donor_type, 'Test Organization');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/non_profit/doctype/membership/test_membership.js b/erpnext/non_profit/doctype/membership/test_membership.js
deleted file mode 100644
index 24c85c61576..00000000000
--- a/erpnext/non_profit/doctype/membership/test_membership.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Membership", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Membership
- () => frappe.tests.make('Membership', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index bf0446bb686..0f6a606cb12 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -295,5 +295,8 @@ erpnext.patches.v13_0.update_tds_check_field #3
erpnext.patches.v13_0.add_custom_field_for_south_africa #2
erpnext.patches.v13_0.update_recipient_email_digest
erpnext.patches.v13_0.shopify_deprecation_warning
+erpnext.patches.v13_0.migrate_stripe_api
+erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
erpnext.patches.v13_0.einvoicing_deprecation_warning
erpnext.patches.v14_0.delete_einvoicing_doctypes
+erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/migrate_stripe_api.py b/erpnext/patches/v13_0/migrate_stripe_api.py
new file mode 100644
index 00000000000..355421a1f42
--- /dev/null
+++ b/erpnext/patches/v13_0/migrate_stripe_api.py
@@ -0,0 +1,7 @@
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+
+def execute():
+ frappe.reload_doc("accounts", "doctype", "subscription_plan")
+ rename_field("Subscription Plan", "payment_plan_id", "product_price_id")
diff --git a/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py b/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py
new file mode 100644
index 00000000000..1da5275761b
--- /dev/null
+++ b/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+ """
+ Reset Clearance Date for Payment Entries of type Internal Transfer that have only been reconciled with one Bank Transaction.
+ This will allow the Payment Entries to be reconciled with the second Bank Transaction using the Bank Reconciliation Tool.
+ """
+
+ intra_company_pe = get_intra_company_payment_entries_with_clearance_dates()
+ reconciled_bank_transactions = get_reconciled_bank_transactions(intra_company_pe)
+
+ for payment_entry in reconciled_bank_transactions:
+ if len(reconciled_bank_transactions[payment_entry]) == 1:
+ frappe.db.set_value('Payment Entry', payment_entry, 'clearance_date', None)
+
+def get_intra_company_payment_entries_with_clearance_dates():
+ return frappe.get_all(
+ 'Payment Entry',
+ filters = {
+ 'payment_type': 'Internal Transfer',
+ 'clearance_date': ["not in", None]
+ },
+ pluck = 'name'
+ )
+
+def get_reconciled_bank_transactions(intra_company_pe):
+ """Returns dictionary where each key:value pair is Payment Entry : List of Bank Transactions reconciled with Payment Entry"""
+
+ reconciled_bank_transactions = {}
+
+ for payment_entry in intra_company_pe:
+ reconciled_bank_transactions[payment_entry] = frappe.get_all(
+ 'Bank Transaction Payments',
+ filters = {
+ 'payment_entry': payment_entry
+ },
+ pluck='parent'
+ )
+
+ return reconciled_bank_transactions
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py b/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py
new file mode 100644
index 00000000000..4acbdd63a00
--- /dev/null
+++ b/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py
@@ -0,0 +1,15 @@
+import frappe
+
+def execute():
+ frappe.reload_doc('manufacturing', 'doctype', 'bom')
+ frappe.reload_doc('manufacturing', 'doctype', 'bom_operation')
+
+ frappe.db.sql('''
+ UPDATE
+ `tabBOM Operation`
+ SET
+ time_in_mins = (operating_cost * 60) / hour_rate
+ WHERE
+ time_in_mins = 0 AND operating_cost > 0
+ AND hour_rate > 0 AND docstatus = 1 AND parenttype = "BOM"
+ ''')
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/test_additional_salary.js b/erpnext/payroll/doctype/additional_salary/test_additional_salary.js
deleted file mode 100644
index c18e1875854..00000000000
--- a/erpnext/payroll/doctype/additional_salary/test_additional_salary.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Additional Salary", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Additional Salary
- () => frappe.tests.make('Additional Salary', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js b/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js
deleted file mode 100644
index b355e1c4366..00000000000
--- a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Benefit Application", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Benefit Application
- () => frappe.tests.make('Employee Benefit Application', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js b/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js
deleted file mode 100644
index 3c808c0a560..00000000000
--- a/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Benefit Claim", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Benefit Claim
- () => frappe.tests.make('Employee Benefit Claim', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js b/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js
deleted file mode 100644
index 10bc03701fd..00000000000
--- a/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Incentive", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Incentive
- () => frappe.tests.make('Employee Incentive', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js b/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js
deleted file mode 100644
index e0e43c32e3b..00000000000
--- a/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Tax Exemption Category", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Tax Exemption Category
- () => frappe.tests.make('Employee Tax Exemption Category', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js
deleted file mode 100644
index 274a3a38603..00000000000
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Tax Exemption Declaration", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Tax Exemption Declaration
- () => frappe.tests.make('Employee Tax Exemption Declaration', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js
deleted file mode 100644
index cec75087280..00000000000
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Tax Exemption Proof Submission", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Tax Exemption Proof Submission
- () => frappe.tests.make('Employee Tax Exemption Proof Submission', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js
deleted file mode 100644
index 8a1a6d151dd..00000000000
--- a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Tax Exemption Sub Category", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Employee Tax Exemption Sub Category
- () => frappe.tests.make('Employee Tax Exemption Sub Category', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/payroll_period/test_payroll_period.js b/erpnext/payroll/doctype/payroll_period/test_payroll_period.js
deleted file mode 100644
index 8c4ded96f38..00000000000
--- a/erpnext/payroll/doctype/payroll_period/test_payroll_period.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Payroll Period", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Payroll Period
- () => frappe.tests.make('Payroll Period', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js b/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js
deleted file mode 100644
index a4b95d3cb10..00000000000
--- a/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Retention Bonus", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Retention Bonus
- () => frappe.tests.make('Retention Bonus', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/salary_component/test_salary_component.js b/erpnext/payroll/doctype/salary_component/test_salary_component.js
deleted file mode 100644
index c47d32d9965..00000000000
--- a/erpnext/payroll/doctype/salary_component/test_salary_component.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Salary Component", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Salary Component
- () => frappe.tests.make('Salary Component', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index d730fcf1fa9..636ec0b5a66 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -828,7 +828,8 @@ def setup_test():
def make_holiday_list():
fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
- if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"):
+ holiday_list = frappe.db.exists("Holiday List", "Salary Slip Test Holiday List")
+ if not holiday_list:
holiday_list = frappe.get_doc({
"doctype": "Holiday List",
"holiday_list_name": "Salary Slip Test Holiday List",
@@ -838,3 +839,6 @@ def make_holiday_list():
}).insert()
holiday_list.get_weekly_off_dates()
holiday_list.save()
+ holiday_list = holiday_list.name
+
+ return holiday_list
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js b/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js
deleted file mode 100644
index 2f52576c7a7..00000000000
--- a/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Salary Structure Assignment", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Salary Structure Assignment
- () => frappe.tests.make('Salary Structure Assignment', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/portal/doctype/products_settings/test_products_settings.js b/erpnext/portal/doctype/products_settings/test_products_settings.js
deleted file mode 100644
index b7049b37e18..00000000000
--- a/erpnext/portal/doctype/products_settings/test_products_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Products Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Products Settings
- () => frappe.tests.make('Products Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/projects/doctype/project/test_project.js b/erpnext/projects/doctype/project/test_project.js
deleted file mode 100644
index 16494f62b60..00000000000
--- a/erpnext/projects/doctype/project/test_project.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Project", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Project
- () => frappe.tests.make('Project', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/projects/doctype/project_type/test_project_type.js b/erpnext/projects/doctype/project_type/test_project_type.js
deleted file mode 100644
index c2198c452c0..00000000000
--- a/erpnext/projects/doctype/project_type/test_project_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Project Type", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially('Project Type', [
- // insert a new Project Type
- () => frappe.tests.make([
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/projects/doctype/project_update/test_project_update.js b/erpnext/projects/doctype/project_update/test_project_update.js
deleted file mode 100644
index bda510b921a..00000000000
--- a/erpnext/projects/doctype/project_update/test_project_update.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Project Update", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Project Update
- () => frappe.tests.make('Project Update', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/projects/doctype/projects_settings/test_projects_settings.js b/erpnext/projects/doctype/projects_settings/test_projects_settings.js
deleted file mode 100644
index f6feaa48049..00000000000
--- a/erpnext/projects/doctype/projects_settings/test_projects_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Projects Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Projects Settings
- () => frappe.tests.make('Projects Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.js b/erpnext/projects/doctype/timesheet/test_timesheet.js
deleted file mode 100644
index c081d6f8ea4..00000000000
--- a/erpnext/projects/doctype/timesheet/test_timesheet.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Timesheet", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new Timesheet
- () => frappe.tests.make('Timesheet', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js b/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js
deleted file mode 100644
index 24c5fd355ff..00000000000
--- a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: GST HSN Code", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new GST HSN Code
- () => frappe.tests.make('GST HSN Code', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/regional/doctype/gst_settings/test_gst_settings.js b/erpnext/regional/doctype/gst_settings/test_gst_settings.js
deleted file mode 100644
index 00fcca6f326..00000000000
--- a/erpnext/regional/doctype/gst_settings/test_gst_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: GST Settings", function (assert) {
- let done = assert.async();
-
- // number of asserts
- assert.expect(1);
-
- frappe.run_serially([
- // insert a new GST Settings
- () => frappe.tests.make('GST Settings', [
- // values to be set
- {key: 'value'}
- ]),
- () => {
- assert.equal(cur_frm.doc.key, 'value');
- },
- () => done()
- ]);
-
-});
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
deleted file mode 100644
index 765b51f435f..00000000000
--- a/erpnext/regional/india/e_invoice/utils.py
+++ /dev/null
@@ -1,1131 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import os
-import re
-import jwt
-import sys
-import json
-import base64
-import frappe
-import six
-import traceback
-import io
-from frappe import _, bold
-from pyqrcode import create as qrcreate
-from frappe.utils.background_jobs import enqueue
-from frappe.utils.scheduler import is_scheduler_inactive
-from frappe.core.page.background_jobs.background_jobs import get_info
-from frappe.integrations.utils import make_post_request, make_get_request
-from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
-from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form, getdate, time_diff_in_hours
-
-@frappe.whitelist()
-def validate_eligibility(doc):
- if isinstance(doc, six.string_types):
- doc = json.loads(doc)
-
- invalid_doctype = doc.get('doctype') != 'Sales Invoice'
- if invalid_doctype:
- return False
-
- einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable'))
- if not einvoicing_enabled:
- return False
-
- einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01'
- if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from):
- return False
-
- invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') })
- invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
- company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
-
- # if export invoice, then taxes can be empty
- # invoice can only be ineligible if no taxes applied and is not an export invoice
- no_taxes_applied = not doc.get('taxes') and not doc.get('gst_category') == 'Overseas'
- has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst'))
-
- if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item:
- return False
-
- return True
-
-def validate_einvoice_fields(doc):
- invoice_eligible = validate_eligibility(doc)
-
- if not invoice_eligible:
- return
-
- if doc.docstatus == 0 and doc._action == 'save':
- if doc.irn:
- frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed'))
- if len(doc.name) > 16:
- raise_document_name_too_long_error()
-
- doc.einvoice_status = 'Pending'
-
- elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
- frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN'))
-
- elif doc.irn and doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled:
- frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed'))
-
-def raise_document_name_too_long_error():
- title = _('Document ID Too Long')
- msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice')
- msg += ', '
- msg += _('document id {} exceed 16 letters.').format(bold(_('should not')))
- msg += '
'
- msg += _('You must {} your {} in order to have document id of {} length 16.').format(
- bold(_('modify')), bold(_('naming series')), bold(_('maximum'))
- )
- msg += _('Please account for ammended documents too.')
- frappe.throw(msg, title=title)
-
-def read_json(name):
- file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name))
- with open(file_path, 'r') as f:
- return cstr(f.read())
-
-def get_transaction_details(invoice):
- supply_type = ''
- if invoice.gst_category == 'Registered Regular': supply_type = 'B2B'
- elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP'
- elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP'
- elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP'
-
- if not supply_type:
- rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export')
- frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export),
- title=_('Invalid Supply Type'))
-
- return frappe._dict(dict(
- tax_scheme='GST',
- supply_type=supply_type,
- reverse_charge=invoice.reverse_charge
- ))
-
-def get_doc_details(invoice):
- if getdate(invoice.posting_date) < getdate('2021-01-01'):
- frappe.throw(_('IRN generation is not allowed for invoices dated before 1st Jan 2021'), title=_('Not Allowed'))
-
- invoice_type = 'CRN' if invoice.is_return else 'INV'
-
- invoice_name = invoice.name
- invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy')
-
- return frappe._dict(dict(
- invoice_type=invoice_type,
- invoice_name=invoice_name,
- invoice_date=invoice_date
- ))
-
-def validate_address_fields(address, is_shipping_address):
- if ((not address.gstin and not is_shipping_address)
- or not address.city
- or not address.pincode
- or not address.address_title
- or not address.address_line1
- or not address.gst_state_number):
-
- frappe.throw(
- msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name),
- title=_('Missing Address Fields')
- )
-
-def get_party_details(address_name, is_shipping_address=False):
- addr = frappe.get_doc('Address', address_name)
-
- validate_address_fields(addr, is_shipping_address)
-
- if addr.gst_state_number == 97:
- # according to einvoice standard
- addr.pincode = 999999
-
- party_address_details = frappe._dict(dict(
- legal_name=sanitize_for_json(addr.address_title),
- location=sanitize_for_json(addr.city),
- pincode=addr.pincode, gstin=addr.gstin,
- state_code=addr.gst_state_number,
- address_line1=sanitize_for_json(addr.address_line1),
- address_line2=sanitize_for_json(addr.address_line2)
- ))
-
- return party_address_details
-
-def get_overseas_address_details(address_name):
- address_title, address_line1, address_line2, city = frappe.db.get_value(
- 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city']
- )
-
- if not address_title or not address_line1 or not city:
- frappe.throw(
- msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format(
- get_link_to_form('Address', address_name)
- ),
- title=_('Missing Address Fields')
- )
-
- return frappe._dict(dict(
- gstin='URP',
- legal_name=sanitize_for_json(address_title),
- location=city,
- address_line1=sanitize_for_json(address_line1),
- address_line2=sanitize_for_json(address_line2),
- pincode=999999, state_code=96, place_of_supply=96
- ))
-
-def get_item_list(invoice):
- item_list = []
-
- for d in invoice.items:
- einvoice_item_schema = read_json('einv_item_template')
- item = frappe._dict({})
- item.update(d.as_dict())
-
- item.sr_no = d.idx
- item.description = sanitize_for_json(d.item_name)
-
- item.qty = abs(item.qty)
- if flt(item.qty) != 0.0:
- item.unit_rate = abs(item.taxable_value / item.qty)
- else:
- item.unit_rate = abs(item.taxable_value)
- item.gross_amount = abs(item.taxable_value)
- item.taxable_value = abs(item.taxable_value)
- item.discount_amount = 0
-
- item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
- item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
- item.is_service_item = 'Y' if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else 'N'
- item.serial_no = ""
-
- item = update_item_taxes(invoice, item)
-
- item.total_value = abs(
- item.taxable_value + item.igst_amount + item.sgst_amount +
- item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges
- )
- einv_item = einvoice_item_schema.format(item=item)
- item_list.append(einv_item)
-
- return ', '.join(item_list)
-
-def update_item_taxes(invoice, item):
- gst_accounts = get_gst_accounts(invoice.company)
- gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
-
- for attr in [
- 'tax_rate', 'cess_rate', 'cess_nadv_amount',
- 'cgst_amount', 'sgst_amount', 'igst_amount',
- 'cess_amount', 'cess_nadv_amount', 'other_charges'
- ]:
- item[attr] = 0
-
- for t in invoice.taxes:
- is_applicable = t.tax_amount and t.account_head in gst_accounts_list
- if is_applicable:
- # this contains item wise tax rate & tax amount (incl. discount)
- item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name)
-
- item_tax_rate = item_tax_detail[0]
- # item tax amount excluding discount amount
- item_tax_amount = (item_tax_rate / 100) * item.taxable_value
-
- if t.account_head in gst_accounts.cess_account:
- item_tax_amount_after_discount = item_tax_detail[1]
- if t.charge_type == 'On Item Quantity':
- item.cess_nadv_amount += abs(item_tax_amount_after_discount)
- else:
- item.cess_rate += item_tax_rate
- item.cess_amount += abs(item_tax_amount_after_discount)
-
- for tax_type in ['igst', 'cgst', 'sgst']:
- if t.account_head in gst_accounts[f'{tax_type}_account']:
- item.tax_rate += item_tax_rate
- item[f'{tax_type}_amount'] += abs(item_tax_amount)
- else:
- # TODO: other charges per item
- pass
-
- return item
-
-def get_invoice_value_details(invoice):
- invoice_value_details = frappe._dict(dict())
- invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
- invoice_value_details.invoice_discount_amt = 0
-
- invoice_value_details.round_off = invoice.base_rounding_adjustment
- invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
- invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
-
- invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
-
- return invoice_value_details
-
-def update_invoice_taxes(invoice, invoice_value_details):
- gst_accounts = get_gst_accounts(invoice.company)
- gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
-
- invoice_value_details.total_cgst_amt = 0
- invoice_value_details.total_sgst_amt = 0
- invoice_value_details.total_igst_amt = 0
- invoice_value_details.total_cess_amt = 0
- invoice_value_details.total_other_charges = 0
- considered_rows = []
-
- for t in invoice.taxes:
- tax_amount = t.base_tax_amount_after_discount_amount
- if t.account_head in gst_accounts_list:
- if t.account_head in gst_accounts.cess_account:
- # using after discount amt since item also uses after discount amt for cess calc
- invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
-
- for tax_type in ['igst', 'cgst', 'sgst']:
- if t.account_head in gst_accounts[f'{tax_type}_account']:
-
- invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount)
- update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows)
- else:
- invoice_value_details.total_other_charges += abs(tax_amount)
-
- return invoice_value_details
-
-def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows):
- prev_row_id = cint(tax_row.row_id) - 1
- if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows:
- if tax_row.charge_type == 'On Previous Row Amount':
- amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount
- invoice_value_details.total_other_charges -= abs(amount)
- considered_rows.append(prev_row_id)
- if tax_row.charge_type == 'On Previous Row Total':
- amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total
- invoice_value_details.total_other_charges -= abs(amount)
- considered_rows.append(prev_row_id)
-
-def get_payment_details(invoice):
- payee_name = invoice.company
- mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
- paid_amount = invoice.base_paid_amount
- outstanding_amount = invoice.outstanding_amount
-
- return frappe._dict(dict(
- payee_name=payee_name, mode_of_payment=mode_of_payment,
- paid_amount=paid_amount, outstanding_amount=outstanding_amount
- ))
-
-def get_return_doc_reference(invoice):
- invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
- return frappe._dict(dict(
- invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
- ))
-
-def get_eway_bill_details(invoice):
- if invoice.is_return:
- frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'),
- title=_('Invalid Fields'))
-
-
- mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
- vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
-
- return frappe._dict(dict(
- gstin=invoice.gst_transporter_id,
- name=invoice.transporter_name,
- mode_of_transport=mode_of_transport[invoice.mode_of_transport],
- distance=invoice.distance or 0,
- document_name=invoice.lr_no,
- document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'),
- vehicle_no=invoice.vehicle_no,
- vehicle_type=vehicle_type[invoice.gst_vehicle_type]
- ))
-
-def validate_mandatory_fields(invoice):
- if not invoice.company_address:
- frappe.throw(
- _('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'),
- title=_('Missing Fields')
- )
- if not invoice.customer_address:
- frappe.throw(
- _('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'),
- title=_('Missing Fields')
- )
- if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
- frappe.throw(
- _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
- title=_('Missing Fields')
- )
- if invoice.gst_category != 'Overseas' and not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
- frappe.throw(
- _('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'),
- title=_('Missing Fields')
- )
-
-def validate_totals(einvoice):
- item_list = einvoice['ItemList']
- value_details = einvoice['ValDtls']
-
- total_item_ass_value = 0
- total_item_cgst_value = 0
- total_item_sgst_value = 0
- total_item_igst_value = 0
- total_item_value = 0
- for item in item_list:
- total_item_ass_value += flt(item['AssAmt'])
- total_item_cgst_value += flt(item['CgstAmt'])
- total_item_sgst_value += flt(item['SgstAmt'])
- total_item_igst_value += flt(item['IgstAmt'])
- total_item_value += flt(item['TotItemVal'])
-
- if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1:
- frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx))
-
- if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
- frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
-
- if abs(
- flt(value_details['TotInvVal']) + flt(value_details['Discount']) -
- flt(value_details['OthChrg']) - flt(value_details['RndOffAmt']) -
- total_item_value) > 1:
- frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
-
- calculated_invoice_value = \
- flt(value_details['AssVal']) + flt(value_details['CgstVal']) \
- + flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \
- + flt(value_details['OthChrg']) + flt(value_details['RndOffAmt']) - flt(value_details['Discount'])
-
- if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1:
- frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.'))
-
-def make_einvoice(invoice):
- validate_mandatory_fields(invoice)
-
- schema = read_json('einv_template')
-
- transaction_details = get_transaction_details(invoice)
- item_list = get_item_list(invoice)
- doc_details = get_doc_details(invoice)
- invoice_value_details = get_invoice_value_details(invoice)
- seller_details = get_party_details(invoice.company_address)
-
- if invoice.gst_category == 'Overseas':
- buyer_details = get_overseas_address_details(invoice.customer_address)
- else:
- buyer_details = get_party_details(invoice.customer_address)
- place_of_supply = get_place_of_supply(invoice, invoice.doctype)
- if place_of_supply:
- place_of_supply = place_of_supply.split('-')[0]
- else:
- place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2]
- buyer_details.update(dict(place_of_supply=place_of_supply))
-
- seller_details.update(dict(legal_name=invoice.company))
- buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer))
-
- shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
- if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
- if invoice.gst_category == 'Overseas':
- shipping_details = get_overseas_address_details(invoice.shipping_address_name)
- else:
- shipping_details = get_party_details(invoice.shipping_address_name, is_shipping_address=True)
-
- if invoice.is_pos and invoice.base_paid_amount:
- payment_details = get_payment_details(invoice)
-
- if invoice.is_return and invoice.return_against:
- prev_doc_details = get_return_doc_reference(invoice)
-
- if invoice.transporter and not invoice.is_return:
- eway_bill_details = get_eway_bill_details(invoice)
-
- # not yet implemented
- dispatch_details = period_details = export_details = frappe._dict({})
-
- einvoice = schema.format(
- transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details,
- seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details,
- item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details,
- period_details=period_details, prev_doc_details=prev_doc_details,
- export_details=export_details, eway_bill_details=eway_bill_details
- )
-
- try:
- einvoice = safe_json_load(einvoice)
- einvoice = santize_einvoice_fields(einvoice)
- except Exception:
- show_link_to_error_log(invoice, einvoice)
-
- validate_totals(einvoice)
-
- return einvoice
-
-def show_link_to_error_log(invoice, einvoice):
- err_log = log_error(einvoice)
- link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log')
- frappe.throw(
- _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
- invoice.name, link_to_error_log),
- title=_('E Invoice Creation Failed')
- )
-
-def log_error(data=None):
- if isinstance(data, six.string_types):
- data = json.loads(data)
-
- seperator = "--" * 50
- err_tb = traceback.format_exc()
- err_msg = str(sys.exc_info()[1])
- data = json.dumps(data, indent=4)
-
- message = "\n".join([
- "Error", err_msg, seperator,
- "Data:", data, seperator,
- "Exception:", err_tb
- ])
- frappe.log_error(title=_('E Invoice Request Failed'), message=message)
-
-def santize_einvoice_fields(einvoice):
- int_fields = ["Pin","Distance","CrDay"]
- float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",]
- copy = einvoice.copy()
- for key, value in copy.items():
- if isinstance(value, list):
- for idx, d in enumerate(value):
- santized_dict = santize_einvoice_fields(d)
- if santized_dict:
- einvoice[key][idx] = santized_dict
- else:
- einvoice[key].pop(idx)
-
- if not einvoice[key]:
- einvoice.pop(key, None)
-
- elif isinstance(value, dict):
- santized_dict = santize_einvoice_fields(value)
- if santized_dict:
- einvoice[key] = santized_dict
- else:
- einvoice.pop(key, None)
-
- elif not value or value == "None":
- einvoice.pop(key, None)
-
- elif key in float_fields:
- einvoice[key] = flt(value, 2)
-
- elif key in int_fields:
- einvoice[key] = cint(value)
-
- return einvoice
-
-def safe_json_load(json_string):
- try:
- return json.loads(json_string)
- except json.JSONDecodeError as e:
- # print a snippet of 40 characters around the location where error occured
- pos = e.pos
- start, end = max(0, pos-20), min(len(json_string)-1, pos+20)
- snippet = json_string[start:end]
- frappe.throw(_("Error in input data. Please check for any special characters near following input:
{}").format(snippet))
-
-class RequestFailed(Exception):
- pass
-class CancellationNotAllowed(Exception):
- pass
-
-class GSPConnector():
- def __init__(self, doctype=None, docname=None):
- self.doctype = doctype
- self.docname = docname
-
- self.set_invoice()
- self.set_credentials()
-
- # authenticate url is same for sandbox & live
- self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token'
- self.base_url = 'https://gsp.adaequare.com' if not self.e_invoice_settings.sandbox_mode else 'https://gsp.adaequare.com/test'
-
- self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
- self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
- self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
- self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
- self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB'
- self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
-
- def set_invoice(self):
- self.invoice = None
- if self.doctype and self.docname:
- self.invoice = frappe.get_cached_doc(self.doctype, self.docname)
-
- def set_credentials(self):
- self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
-
- if not self.e_invoice_settings.enable:
- frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings")))
-
- if self.invoice:
- gstin = self.get_seller_gstin()
- credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin]
- if credentials_for_gstin:
- self.credentials = credentials_for_gstin[0]
- else:
- frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings'))
- else:
- self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
-
- def get_seller_gstin(self):
- gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
- if not gstin:
- frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
- return gstin
-
- def get_auth_token(self):
- if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0:
- self.fetch_auth_token()
-
- return self.e_invoice_settings.auth_token
-
- def make_request(self, request_type, url, headers=None, data=None):
- if request_type == 'post':
- res = make_post_request(url, headers=headers, data=data)
- else:
- res = make_get_request(url, headers=headers, data=data)
-
- self.log_request(url, headers, data, res)
- return res
-
- def log_request(self, url, headers, data, res):
- headers.update({ 'password': self.credentials.password })
- request_log = frappe.get_doc({
- "doctype": "E Invoice Request Log",
- "user": frappe.session.user,
- "reference_invoice": self.invoice.name if self.invoice else None,
- "url": url,
- "headers": json.dumps(headers, indent=4) if headers else None,
- "data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
- "response": json.dumps(res, indent=4) if res else None
- })
- request_log.save(ignore_permissions=True)
- frappe.db.commit()
-
- def fetch_auth_token(self):
- headers = {
- 'gspappid': frappe.conf.einvoice_client_id,
- 'gspappsecret': frappe.conf.einvoice_client_secret
- }
- res = {}
- try:
- res = self.make_request('post', self.authenticate_url, headers)
- self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
- self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
- self.e_invoice_settings.save(ignore_permissions=True)
- self.e_invoice_settings.reload()
-
- except Exception:
- log_error(res)
- self.raise_error(True)
-
- def get_headers(self):
- return {
- 'content-type': 'application/json',
- 'user_name': self.credentials.username,
- 'password': self.credentials.get_password(),
- 'gstin': self.credentials.gstin,
- 'authorization': self.get_auth_token(),
- 'requestid': str(base64.b64encode(os.urandom(18))),
- }
-
- def fetch_gstin_details(self, gstin):
- headers = self.get_headers()
-
- try:
- params = '?gstin={gstin}'.format(gstin=gstin)
- res = self.make_request('get', self.gstin_details_url + params, headers)
- if res.get('success'):
- return res.get('result')
- else:
- log_error(res)
- raise RequestFailed
-
- except RequestFailed:
- self.raise_error()
-
- except Exception:
- log_error()
- self.raise_error(True)
- @staticmethod
- def get_gstin_details(gstin):
- '''fetch and cache GSTIN details'''
- if not hasattr(frappe.local, 'gstin_cache'):
- frappe.local.gstin_cache = {}
-
- key = gstin
- gsp_connector = GSPConnector()
- details = gsp_connector.fetch_gstin_details(gstin)
-
- frappe.local.gstin_cache[key] = details
- frappe.cache().hset('gstin_cache', key, details)
- return details
-
- def generate_irn(self):
- data = {}
- try:
- headers = self.get_headers()
- einvoice = make_einvoice(self.invoice)
- data = json.dumps(einvoice, indent=4)
- res = self.make_request('post', self.generate_irn_url, headers, data)
-
- if res.get('success'):
- self.set_einvoice_data(res.get('result'))
-
- elif '2150' in res.get('message'):
- # IRN already generated but not updated in invoice
- # Extract the IRN from the response description and fetch irn details
- irn = res.get('result')[0].get('Desc').get('Irn')
- irn_details = self.get_irn_details(irn)
- if irn_details:
- self.set_einvoice_data(irn_details)
- else:
- raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \
- Contact ERPNext support to resolve the issue.')
-
- else:
- raise RequestFailed
-
- except RequestFailed:
- errors = self.sanitize_error_message(res.get('message'))
- self.set_failed_status(errors=errors)
- self.raise_error(errors=errors)
-
- except Exception as e:
- self.set_failed_status(errors=str(e))
- log_error(data)
- self.raise_error(True)
-
- @staticmethod
- def bulk_generate_irn(invoices):
- gsp_connector = GSPConnector()
- gsp_connector.doctype = 'Sales Invoice'
-
- failed = []
-
- for invoice in invoices:
- try:
- gsp_connector.docname = invoice
- gsp_connector.set_invoice()
- gsp_connector.set_credentials()
- gsp_connector.generate_irn()
-
- except Exception as e:
- failed.append({
- 'docname': invoice,
- 'message': str(e)
- })
-
- return failed
-
- def get_irn_details(self, irn):
- headers = self.get_headers()
-
- try:
- params = '?irn={irn}'.format(irn=irn)
- res = self.make_request('get', self.irn_details_url + params, headers)
- if res.get('success'):
- return res.get('result')
- else:
- raise RequestFailed
-
- except RequestFailed:
- errors = self.sanitize_error_message(res.get('message'))
- self.raise_error(errors=errors)
-
- except Exception:
- log_error()
- self.raise_error(True)
-
- def cancel_irn(self, irn, reason, remark):
- data, res = {}, {}
- try:
- # validate cancellation
- if time_diff_in_hours(now_datetime(), self.invoice.ack_date) > 24:
- frappe.throw(_('E-Invoice cannot be cancelled after 24 hours of IRN generation.'), title=_('Not Allowed'), exc=CancellationNotAllowed)
- if not irn:
- frappe.throw(_('IRN not found. You must generate IRN before cancelling.'), title=_('Not Allowed'), exc=CancellationNotAllowed)
-
- headers = self.get_headers()
- data = json.dumps({
- 'Irn': irn,
- 'Cnlrsn': reason,
- 'Cnlrem': remark
- }, indent=4)
-
- res = self.make_request('post', self.cancel_irn_url, headers, data)
- if res.get('success') or '9999' in res.get('message'):
- self.invoice.irn_cancelled = 1
- self.invoice.irn_cancel_date = res.get('result')['CancelDate'] if res.get('result') else ""
- self.invoice.einvoice_status = 'Cancelled'
- self.invoice.flags.updater_reference = {
- 'doctype': self.invoice.doctype,
- 'docname': self.invoice.name,
- 'label': _('IRN Cancelled - {}').format(remark)
- }
- self.update_invoice()
-
- else:
- raise RequestFailed
-
- except RequestFailed:
- errors = self.sanitize_error_message(res.get('message'))
- self.set_failed_status(errors=errors)
- self.raise_error(errors=errors)
-
- except CancellationNotAllowed as e:
- self.set_failed_status(errors=str(e))
- self.raise_error(errors=str(e))
-
- except Exception as e:
- self.set_failed_status(errors=str(e))
- log_error(data)
- self.raise_error(True)
-
- @staticmethod
- def bulk_cancel_irn(invoices, reason, remark):
- gsp_connector = GSPConnector()
- gsp_connector.doctype = 'Sales Invoice'
-
- failed = []
-
- for invoice in invoices:
- try:
- gsp_connector.docname = invoice
- gsp_connector.set_invoice()
- gsp_connector.set_credentials()
- irn = gsp_connector.invoice.irn
- gsp_connector.cancel_irn(irn, reason, remark)
-
- except Exception as e:
- failed.append({
- 'docname': invoice,
- 'message': str(e)
- })
-
- return failed
-
- def generate_eway_bill(self, **kwargs):
- args = frappe._dict(kwargs)
-
- headers = self.get_headers()
- eway_bill_details = get_eway_bill_details(args)
- data = json.dumps({
- 'Irn': args.irn,
- 'Distance': cint(eway_bill_details.distance),
- 'TransMode': eway_bill_details.mode_of_transport,
- 'TransId': eway_bill_details.gstin,
- 'TransName': eway_bill_details.transporter,
- 'TrnDocDt': eway_bill_details.document_date,
- 'TrnDocNo': eway_bill_details.document_name,
- 'VehNo': eway_bill_details.vehicle_no,
- 'VehType': eway_bill_details.vehicle_type
- }, indent=4)
-
- try:
- res = self.make_request('post', self.generate_ewaybill_url, headers, data)
- if res.get('success'):
- self.invoice.ewaybill = res.get('result').get('EwbNo')
- self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill')
- self.invoice.eway_bill_cancelled = 0
- self.invoice.update(args)
- self.invoice.flags.updater_reference = {
- 'doctype': self.invoice.doctype,
- 'docname': self.invoice.name,
- 'label': _('E-Way Bill Generated')
- }
- self.update_invoice()
-
- else:
- raise RequestFailed
-
- except RequestFailed:
- errors = self.sanitize_error_message(res.get('message'))
- self.raise_error(errors=errors)
-
- except Exception:
- log_error(data)
- self.raise_error(True)
-
- def cancel_eway_bill(self, eway_bill, reason, remark):
- headers = self.get_headers()
- data = json.dumps({
- 'ewbNo': eway_bill,
- 'cancelRsnCode': reason,
- 'cancelRmrk': remark
- }, indent=4)
- headers["username"] = headers["user_name"]
- del headers["user_name"]
- try:
- res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
- if res.get('success'):
- self.invoice.ewaybill = ''
- self.invoice.eway_bill_cancelled = 1
- self.invoice.flags.updater_reference = {
- 'doctype': self.invoice.doctype,
- 'docname': self.invoice.name,
- 'label': _('E-Way Bill Cancelled - {}').format(remark)
- }
- self.update_invoice()
-
- else:
- raise RequestFailed
-
- except RequestFailed:
- errors = self.sanitize_error_message(res.get('message'))
- self.raise_error(errors=errors)
-
- except Exception:
- log_error(data)
- self.raise_error(True)
-
- def sanitize_error_message(self, message):
- '''
- On validation errors, response message looks something like this:
- message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable,
- 3095 : Supplier GSTIN is inactive'
- we search for string between ':' to extract the error messages
- errors = [
- ': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ',
- ': Test'
- ]
- then we trim down the message by looping over errors
- '''
- if not message:
- return []
-
- errors = re.findall(': [^:]+', message)
- for idx, e in enumerate(errors):
- # remove colons
- errors[idx] = errors[idx].replace(':', '').strip()
- # if not last
- if idx != len(errors) - 1:
- # remove last 7 chars eg: ', 3095 '
- errors[idx] = errors[idx][:-6]
-
- return errors
-
- def raise_error(self, raise_exception=False, errors=[]):
- title = _('E Invoice Request Failed')
- if errors:
- frappe.throw(errors, title=title, as_list=1)
- else:
- link_to_error_list = 'Error Log'
- frappe.msgprint(
- _('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list),
- title=title,
- raise_exception=raise_exception,
- indicator='red'
- )
-
- def set_einvoice_data(self, res):
- enc_signed_invoice = res.get('SignedInvoice')
- dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})['data']
-
- self.invoice.irn = res.get('Irn')
- self.invoice.ewaybill = res.get('EwbNo')
- self.invoice.eway_bill_validity = res.get('EwbValidTill')
- self.invoice.ack_no = res.get('AckNo')
- self.invoice.ack_date = res.get('AckDt')
- self.invoice.signed_einvoice = dec_signed_invoice
- self.invoice.ack_no = res.get('AckNo')
- self.invoice.ack_date = res.get('AckDt')
- self.invoice.signed_qr_code = res.get('SignedQRCode')
- self.invoice.einvoice_status = 'Generated'
-
- self.attach_qrcode_image()
-
- self.invoice.flags.updater_reference = {
- 'doctype': self.invoice.doctype,
- 'docname': self.invoice.name,
- 'label': _('IRN Generated')
- }
- self.update_invoice()
-
- def attach_qrcode_image(self):
- qrcode = self.invoice.signed_qr_code
- doctype = self.invoice.doctype
- docname = self.invoice.name
- filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__")
-
- qr_image = io.BytesIO()
- url = qrcreate(qrcode, error='L')
- url.png(qr_image, scale=2, quiet_zone=1)
- _file = frappe.get_doc({
- "doctype": "File",
- "file_name": filename,
- "attached_to_doctype": doctype,
- "attached_to_name": docname,
- "attached_to_field": "qrcode_image",
- "is_private": 0,
- "content": qr_image.getvalue()})
- _file.save()
- frappe.db.commit()
- self.invoice.qrcode_image = _file.file_url
-
- def update_invoice(self):
- self.invoice.flags.ignore_validate_update_after_submit = True
- self.invoice.flags.ignore_validate = True
- self.invoice.save()
-
- def set_failed_status(self, errors=None):
- frappe.db.rollback()
- self.invoice.einvoice_status = 'Failed'
- self.invoice.failure_description = self.get_failure_message(errors) if errors else ""
- self.update_invoice()
- frappe.db.commit()
-
- def get_failure_message(self, errors):
- if isinstance(errors, list):
- errors = ', '.join(errors)
- return errors
-
-def sanitize_for_json(string):
- """Escape JSON specific characters from a string."""
-
- # json.dumps adds double-quotes to the string. Indexing to remove them.
- return json.dumps(string)[1:-1]
-
-@frappe.whitelist()
-def get_einvoice(doctype, docname):
- invoice = frappe.get_doc(doctype, docname)
- return make_einvoice(invoice)
-
-@frappe.whitelist()
-def generate_irn(doctype, docname):
- gsp_connector = GSPConnector(doctype, docname)
- gsp_connector.generate_irn()
-
-@frappe.whitelist()
-def cancel_irn(doctype, docname, irn, reason, remark):
- gsp_connector = GSPConnector(doctype, docname)
- gsp_connector.cancel_irn(irn, reason, remark)
-
-@frappe.whitelist()
-def generate_eway_bill(doctype, docname, **kwargs):
- gsp_connector = GSPConnector(doctype, docname)
- gsp_connector.generate_eway_bill(**kwargs)
-
-@frappe.whitelist()
-def cancel_eway_bill(doctype, docname):
- # TODO: uncomment when eway_bill api from Adequare is enabled
- # gsp_connector = GSPConnector(doctype, docname)
- # gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
-
- frappe.db.set_value(doctype, docname, 'ewaybill', '')
- frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1)
-
-@frappe.whitelist()
-def generate_einvoices(docnames):
- docnames = json.loads(docnames) or []
-
- if len(docnames) < 10:
- failures = GSPConnector.bulk_generate_irn(docnames)
- frappe.local.message_log = []
-
- if failures:
- show_bulk_action_failure_message(failures)
-
- success = len(docnames) - len(failures)
- frappe.msgprint(
- _('{} e-invoices generated successfully').format(success),
- title=_('Bulk E-Invoice Generation Complete')
- )
-
- else:
- enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames)
-
-def schedule_bulk_generate_irn(docnames):
- failures = GSPConnector.bulk_generate_irn(docnames)
- frappe.local.message_log = []
-
- frappe.publish_realtime("bulk_einvoice_generation_complete", {
- "user": frappe.session.user,
- "failures": failures,
- "invoices": docnames
- })
-
-def show_bulk_action_failure_message(failures):
- for doc in failures:
- docname = '{0}'.format(doc.get('docname'))
- message = doc.get('message').replace("'", '"')
- if message[0] == '[':
- errors = json.loads(message)
- error_list = ''.join(['