fix: merge conflict
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 824b74e..84ecfb1 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -80,14 +80,29 @@
env:
TYPE: ${{ matrix.TYPE }}
- - name: Coverage
- if: matrix.TYPE == 'server'
+ - name: Coverage - Pull Request
+ if: matrix.TYPE == 'server' && github.event_name == 'pull_request'
run: |
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
cd ${GITHUB_WORKSPACE}
- pip install coveralls==3.0.1
- pip install coverage==5.5
+ pip install coveralls==2.2.0
+ pip install coverage==4.5.4
coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
+ COVERALLS_SERVICE_NAME: github
+
+ - name: Coverage - Push
+ if: matrix.TYPE == 'server' && github.event_name == 'push'
+ run: |
+ cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
+ cd ${GITHUB_WORKSPACE}
+ pip install coveralls==2.2.0
+ pip install coverage==4.5.4
+ coveralls --service=github-actions
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
+ COVERALLS_SERVICE_NAME: github-actions
+
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index e1276e7..781f94e 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -7,26 +7,30 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "auto_accounting_for_stock",
- "acc_frozen_upto",
- "frozen_accounts_modifier",
- "determine_address_tax_category_from",
+ "accounts_transactions_settings_section",
"over_billing_allowance",
"role_allowed_to_over_bill",
- "column_break_4",
- "credit_controller",
- "check_supplier_invoice_uniqueness",
"make_payment_via_journal_entry",
+ "column_break_11",
+ "check_supplier_invoice_uniqueness",
"unlink_payment_on_cancellation_of_invoice",
- "unlink_advance_payment_on_cancelation_of_order",
- "book_asset_depreciation_entry_automatically",
- "add_taxes_from_item_tax_template",
"automatically_fetch_payment_terms",
"delete_linked_ledger_entries",
+ "book_asset_depreciation_entry_automatically",
+ "unlink_advance_payment_on_cancelation_of_order",
+ "tax_settings_section",
+ "determine_address_tax_category_from",
+ "column_break_19",
+ "add_taxes_from_item_tax_template",
+ "period_closing_settings_section",
+ "acc_frozen_upto",
+ "frozen_accounts_modifier",
+ "column_break_4",
+ "credit_controller",
"deferred_accounting_settings_section",
- "automatically_process_deferred_accounting_entry",
"book_deferred_entries_based_on",
"column_break_18",
+ "automatically_process_deferred_accounting_entry",
"book_deferred_entries_via_journal_entry",
"submit_journal_entries",
"print_settings",
@@ -41,15 +45,6 @@
],
"fields": [
{
- "default": "1",
- "description": "If enabled, the system will post accounting entries for inventory automatically",
- "fieldname": "auto_accounting_for_stock",
- "fieldtype": "Check",
- "hidden": 1,
- "in_list_view": 1,
- "label": "Make Accounting Entry For Every Stock Movement"
- },
- {
"description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below",
"fieldname": "acc_frozen_upto",
"fieldtype": "Date",
@@ -94,6 +89,7 @@
"default": "0",
"fieldname": "make_payment_via_journal_entry",
"fieldtype": "Check",
+ "hidden": 1,
"label": "Make Payment via Journal Entry"
},
{
@@ -234,6 +230,29 @@
"fieldtype": "Link",
"label": "Role Allowed to Over Bill ",
"options": "Role"
+ },
+ {
+ "fieldname": "period_closing_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Period Closing Settings"
+ },
+ {
+ "fieldname": "accounts_transactions_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Transactions Settings"
+ },
+ {
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "tax_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Tax Settings"
+ },
+ {
+ "fieldname": "column_break_19",
+ "fieldtype": "Column Break"
}
],
"icon": "icon-cog",
@@ -241,7 +260,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-03-11 18:52:05.601996",
+ "modified": "2021-04-30 15:25:10.381008",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py
index cb18309..c5ce514 100644
--- a/erpnext/accounts/doctype/dunning/test_dunning.py
+++ b/erpnext/accounts/doctype/dunning/test_dunning.py
@@ -42,9 +42,9 @@
['Sales - _TC', 0.0, 20.44]
])
for gle in gl_entries:
- self.assertEquals(expected_values[gle.account][0], gle.account)
- self.assertEquals(expected_values[gle.account][1], gle.debit)
- self.assertEquals(expected_values[gle.account][2], gle.credit)
+ self.assertEqual(expected_values[gle.account][0], gle.account)
+ self.assertEqual(expected_values[gle.account][1], gle.debit)
+ self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_payment_entry(self):
dunning = create_dunning()
diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py
index b4a547b..4167ca7 100644
--- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py
@@ -54,4 +54,4 @@
self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries)))
new_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0]
- self.assertEquals(old_naming_series_current_value + 2, new_naming_series_current_value)
+ self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value)
diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json
index aa32d95..c9f15a6 100644
--- a/erpnext/accounts/doctype/party_account/party_account.json
+++ b/erpnext/accounts/doctype/party_account/party_account.json
@@ -1,87 +1,39 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2014-08-29 16:02:39.740505",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2014-08-29 16:02:39.740505",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "field_order": [
+ "company",
+ "account"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account",
+ "options": "Account"
}
- ],
- "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": "2016-07-11 03:28:03.348246",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Party Account",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_seen": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-07 18:13:08.833822",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Party Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py
index 1c23e2a..5fdde07 100644
--- a/erpnext/accounts/doctype/payment_order/test_payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py
@@ -31,10 +31,10 @@
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
reference_doc = doc.get("references")[0]
- self.assertEquals(reference_doc.reference_name, payment_entry.name)
- self.assertEquals(reference_doc.reference_doctype, "Payment Entry")
- self.assertEquals(reference_doc.supplier, "_Test Supplier")
- self.assertEquals(reference_doc.amount, 250)
+ self.assertEqual(reference_doc.reference_name, payment_entry.name)
+ self.assertEqual(reference_doc.reference_doctype, "Payment Entry")
+ self.assertEqual(reference_doc.supplier, "_Test Supplier")
+ self.assertEqual(reference_doc.amount, 250)
def create_payment_order_against_payment_entry(ref_doc, order_type):
payment_order = frappe.get_doc(dict(
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index cf6ec18..6635128 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -114,7 +114,7 @@
'party_type': self.party_type,
'voucher_type': voucher_type,
'account': self.receivable_payable_account
- }, as_dict=1, debug=1)
+ }, as_dict=1)
def add_payment_entries(self, entries):
self.set('payments', [])
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 1e6a3d1..473db56 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -461,7 +461,17 @@
order by posting_date desc, posting_time desc
limit 1""", (item_code, warehouse), as_dict=1)
- pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty
+ pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
+
+ sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
+
+ if sle_qty and pos_sales_qty:
+ return sle_qty - pos_sales_qty
+ else:
+ return sle_qty
+
+def get_pos_reserved_qty(item_code, warehouse):
+ reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
where p.name = p_item.parent
and p.consolidated_invoice is NULL
@@ -470,14 +480,8 @@
and p_item.item_code = %s
and p_item.warehouse = %s
""", (item_code, warehouse), as_dict=1)
-
- sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
- pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
-
- if sle_qty and pos_sales_qty:
- return sle_qty - pos_sales_qty
- else:
- return sle_qty
+
+ return reserved_qty[0].qty or 0 if reserved_qty else 0
@frappe.whitelist()
def make_sales_return(source_name, target_doc=None):
diff --git a/erpnext/accounts/doctype/pos_search_fields/__init__.py b/erpnext/accounts/doctype/pos_search_fields/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_search_fields/__init__.py
diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json
new file mode 100644
index 0000000..a627f5b
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "creation": "2021-04-19 14:56:06.652327",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "field",
+ "fieldname"
+ ],
+ "fields": [
+ {
+ "fieldname": "fieldname",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Fieldname"
+ },
+ {
+ "fieldname": "field",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Field"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-21 11:12:54.632093",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "POS Search Fields",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py
new file mode 100644
index 0000000..720ea77
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, 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 POSSearchFields(Document):
+ pass
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js
index 3625393..9003af5 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.js
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js
@@ -1,9 +1,17 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
+let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor'];
+let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series",
+ "default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series",
+ "serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account",
+ "deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail",
+ "web_long_description", "hub_sync_id"]
+
frappe.ui.form.on('POS Settings', {
onload: function(frm) {
frm.trigger("get_invoice_fields");
+ frm.trigger("add_search_options");
},
get_invoice_fields: function(frm) {
@@ -21,6 +29,38 @@
);
});
+ },
+
+ add_search_options: function(frm) {
+ frappe.model.with_doctype("Item", () => {
+ var fields = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
+ if (search_fields_datatypes.includes(d.fieldtype) && !(do_not_include_fields.includes(d.fieldname))) {
+ return [d.label];
+ } else {
+ return null;
+ }
+ });
+
+ fields.unshift('');
+ frm.fields_dict.pos_search_fields.grid.update_docfield_property('field', 'options', fields);
+ });
+
+ }
+});
+
+frappe.ui.form.on("POS Search Fields", {
+ field: function(frm, doctype, name) {
+ var doc = frappe.get_doc(doctype, name);
+ var df = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
+ if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) {
+ return d;
+ } else {
+ return null;
+ }
+ })[0];
+
+ doc.fieldname = df.fieldname;
+ frm.refresh_field("fields");
}
});
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json
index 3539588..962eb94 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.json
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json
@@ -5,7 +5,8 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "invoice_fields"
+ "invoice_fields",
+ "pos_search_fields"
],
"fields": [
{
@@ -13,11 +14,17 @@
"fieldtype": "Table",
"label": "POS Field",
"options": "POS Field"
+ },
+ {
+ "fieldname": "pos_search_fields",
+ "fieldtype": "Table",
+ "label": "POS Search Fields",
+ "options": "POS Search Fields"
}
],
"issingle": 1,
"links": [],
- "modified": "2020-06-01 15:46:41.478928",
+ "modified": "2021-04-19 14:56:24.465218",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Settings",
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index ef9aad5..ffe8be1 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -99,7 +99,7 @@
args.item_code = "_Test Item 2"
details = get_item_details(args)
- self.assertEquals(details.get("discount_percentage"), 15)
+ self.assertEqual(details.get("discount_percentage"), 15)
def test_pricing_rule_for_margin(self):
from erpnext.stock.get_item_details import get_item_details
@@ -145,8 +145,8 @@
"name": None
})
details = get_item_details(args)
- self.assertEquals(details.get("margin_type"), "Percentage")
- self.assertEquals(details.get("margin_rate_or_amount"), 10)
+ self.assertEqual(details.get("margin_type"), "Percentage")
+ self.assertEqual(details.get("margin_rate_or_amount"), 10)
def test_mixed_conditions_for_item_group(self):
for item in ["Mixed Cond Item 1", "Mixed Cond Item 2"]:
@@ -192,7 +192,7 @@
"name": None
})
details = get_item_details(args)
- self.assertEquals(details.get("discount_percentage"), 10)
+ self.assertEqual(details.get("discount_percentage"), 10)
def test_pricing_rule_for_variants(self):
from erpnext.stock.get_item_details import get_item_details
@@ -322,11 +322,11 @@
si.insert(ignore_permissions=True)
item = si.items[0]
- self.assertEquals(item.margin_rate_or_amount, 10)
- self.assertEquals(item.rate_with_margin, 1100)
+ self.assertEqual(item.margin_rate_or_amount, 10)
+ self.assertEqual(item.rate_with_margin, 1100)
self.assertEqual(item.discount_percentage, 10)
- self.assertEquals(item.discount_amount, 110)
- self.assertEquals(item.rate, 990)
+ self.assertEqual(item.discount_amount, 110)
+ self.assertEqual(item.rate, 990)
def test_pricing_rule_with_margin_and_discount_amount(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
@@ -338,10 +338,10 @@
si.insert(ignore_permissions=True)
item = si.items[0]
- self.assertEquals(item.margin_rate_or_amount, 10)
- self.assertEquals(item.rate_with_margin, 1100)
- self.assertEquals(item.discount_amount, 110)
- self.assertEquals(item.rate, 990)
+ self.assertEqual(item.margin_rate_or_amount, 10)
+ self.assertEqual(item.rate_with_margin, 1100)
+ self.assertEqual(item.discount_amount, 110)
+ self.assertEqual(item.rate, 990)
def test_pricing_rule_for_product_discount_on_same_item(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
@@ -458,21 +458,21 @@
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
- self.assertEquals(item.rate, 100)
+ self.assertEqual(item.rate, 100)
# Correct Customer and Incorrect is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
- self.assertEquals(item.rate, 100)
+ self.assertEqual(item.rate, 100)
# Correct Customer and correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
si.items[0].price_list_rate = 1000
si.submit()
item = si.items[0]
- self.assertEquals(item.rate, 900)
+ self.assertEqual(item.rate, 900)
def test_multiple_pricing_rules(self):
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
@@ -545,11 +545,11 @@
apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
si = create_sales_invoice(qty=5, do_not_submit=True)
- self.assertEquals(len(si.items), 2)
- self.assertEquals(si.items[1].rate, 10)
+ self.assertEqual(len(si.items), 2)
+ self.assertEqual(si.items[1].rate, 10)
si1 = create_sales_invoice(qty=2, do_not_submit=True)
- self.assertEquals(len(si1.items), 1)
+ self.assertEqual(len(si1.items), 1)
for doc in [si, si1]:
doc.delete()
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index e61cde8..f58c8f4 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -514,6 +514,28 @@
}
},
+ refresh: function(frm) {
+ frm.events.add_custom_buttons(frm);
+ },
+
+ add_custom_buttons: function(frm) {
+ if (frm.doc.per_received < 100) {
+ frm.add_custom_button(__('Purchase Receipt'), () => {
+ frm.events.make_purchase_receipt(frm);
+ }, __('Create'));
+ }
+
+ if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) {
+ frm.add_custom_button(__('Purchase Receipt'), () => {
+ frappe.route_options = {
+ 'purchase_invoice': frm.doc.name
+ }
+
+ frappe.set_route("List", "Purchase Receipt", "List")
+ }, __('View'));
+ }
+ },
+
onload: function(frm) {
if(frm.doc.__onload && frm.is_new()) {
if(frm.doc.supplier) {
@@ -539,5 +561,13 @@
update_stock: function(frm) {
hide_fields(frm.doc);
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
+ },
+
+ make_purchase_receipt: function(frm) {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
+ frm: frm,
+ freeze_message: __("Creating Purchase Receipt ...")
+ })
}
})
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 2d5760b..24e67fe 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -163,7 +163,8 @@
"to_date",
"column_break_114",
"auto_repeat",
- "update_auto_repeat_reference"
+ "update_auto_repeat_reference",
+ "per_received"
],
"fields": [
{
@@ -1364,6 +1365,15 @@
"print_hide": 1,
"print_width": "50px",
"width": "50px"
+ },
+ {
+ "fieldname": "per_received",
+ "fieldtype": "Percent",
+ "hidden": 1,
+ "label": "Per Received",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 5c4e32e..83e9f75 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1207,3 +1207,41 @@
def on_doctype_update():
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
+
+@frappe.whitelist()
+def make_purchase_receipt(source_name, target_doc=None):
+ def update_item(obj, target, source_parent):
+ target.qty = flt(obj.qty) - flt(obj.received_qty)
+ target.received_qty = flt(obj.qty) - flt(obj.received_qty)
+ target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
+ target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
+ target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
+ flt(obj.rate) * flt(source_parent.conversion_rate)
+
+ doc = get_mapped_doc("Purchase Invoice", source_name, {
+ "Purchase Invoice": {
+ "doctype": "Purchase Receipt",
+ "validation": {
+ "docstatus": ["=", 1],
+ }
+ },
+ "Purchase Invoice Item": {
+ "doctype": "Purchase Receipt Item",
+ "field_map": {
+ "name": "purchase_invoice_item",
+ "parent": "purchase_invoice",
+ "bom": "bom",
+ "purchase_order": "purchase_order",
+ "po_detail": "purchase_order_item",
+ "material_request": "material_request",
+ "material_request_item": "material_request_item"
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
+ },
+ "Purchase Taxes and Charges": {
+ "doctype": "Purchase Taxes and Charges"
+ }
+ }, target_doc)
+
+ return doc
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 96ad0fd..10e1c73 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -607,6 +607,7 @@
"oldfieldname": "purchase_order",
"oldfieldtype": "Link",
"options": "Purchase Order",
+ "print_hide": 1,
"read_only": 1,
"search_index": 1
},
@@ -853,7 +854,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-23 00:59:52.614805",
+ "modified": "2021-03-30 09:02:39.256602",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 8a42d9e..7c73ad6 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -356,11 +356,11 @@
},
items_on_form_rendered: function() {
- erpnext.setup_serial_no();
+ erpnext.setup_serial_or_batch_no();
},
packed_items_on_form_rendered: function(doc, grid_row) {
- erpnext.setup_serial_no();
+ erpnext.setup_serial_or_batch_no();
},
make_sales_return: function() {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 4461f29..bb74a02 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1111,7 +1111,7 @@
if not item.serial_no:
continue
- for serial_no in item.serial_no.split("\n"):
+ for serial_no in get_serial_nos(item.serial_no):
if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code:
frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice)
@@ -1755,15 +1755,10 @@
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
def get_delivery_note_details(internal_reference):
- so_item_map = {}
-
si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
filters={'parent': internal_reference})
- for d in si_item_details:
- so_item_map.setdefault(d.name, d.so_detail)
-
- return so_item_map
+ return {d.name: d.so_detail for d in si_item_details if d.so_detail}
def get_sales_invoice_details(internal_reference):
dn_item_map = {}
diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json
index e80df2a..c4e4be7 100644
--- a/erpnext/accounts/doctype/subscription/subscription.json
+++ b/erpnext/accounts/doctype/subscription/subscription.json
@@ -36,6 +36,7 @@
"additional_discount_percentage",
"additional_discount_amount",
"sb_3",
+ "submit_invoice",
"invoices",
"accounting_dimensions_section",
"cost_center",
@@ -45,9 +46,7 @@
{
"allow_on_submit": 1,
"fieldname": "cb_1",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "status",
@@ -55,97 +54,73 @@
"label": "Status",
"no_copy": 1,
"options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "subscription_period",
"fieldtype": "Section Break",
- "label": "Subscription Period",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Subscription Period"
},
{
"fieldname": "cancelation_date",
"fieldtype": "Date",
"label": "Cancelation Date",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"allow_on_submit": 1,
"fieldname": "trial_period_start",
"fieldtype": "Date",
"label": "Trial Period Start Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"depends_on": "eval:doc.trial_period_start",
"fieldname": "trial_period_end",
"fieldtype": "Date",
"label": "Trial Period End Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "current_invoice_start",
"fieldtype": "Date",
"label": "Current Invoice Start Date",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "current_invoice_end",
"fieldtype": "Date",
"label": "Current Invoice End Date",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"default": "0",
"description": "Number of days that the subscriber has to pay invoices generated by this subscription",
"fieldname": "days_until_due",
"fieldtype": "Int",
- "label": "Days Until Due",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Days Until Due"
},
{
"default": "0",
"fieldname": "cancel_at_period_end",
"fieldtype": "Check",
- "label": "Cancel At End Of Period",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Cancel At End Of Period"
},
{
"default": "0",
"fieldname": "generate_invoice_at_period_start",
"fieldtype": "Check",
- "label": "Generate Invoice At Beginning Of Period",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Generate Invoice At Beginning Of Period"
},
{
"allow_on_submit": 1,
"fieldname": "sb_4",
"fieldtype": "Section Break",
- "label": "Plans",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Plans"
},
{
"allow_on_submit": 1,
@@ -153,84 +128,62 @@
"fieldtype": "Table",
"label": "Plans",
"options": "Subscription Plan Detail",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)",
"fieldname": "sb_1",
"fieldtype": "Section Break",
- "label": "Taxes",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Taxes"
},
{
"fieldname": "sb_2",
"fieldtype": "Section Break",
- "label": "Discounts",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Discounts"
},
{
"fieldname": "apply_additional_discount",
"fieldtype": "Select",
"label": "Apply Additional Discount On",
- "options": "\nGrand Total\nNet Total",
- "show_days": 1,
- "show_seconds": 1
+ "options": "\nGrand Total\nNet Total"
},
{
"fieldname": "cb_2",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Percent",
- "label": "Additional DIscount Percentage",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Additional DIscount Percentage"
},
{
"collapsible": 1,
"fieldname": "additional_discount_amount",
"fieldtype": "Currency",
- "label": "Additional DIscount Amount",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Additional DIscount Amount"
},
{
"depends_on": "eval:doc.invoices",
"fieldname": "sb_3",
"fieldtype": "Section Break",
- "label": "Invoices",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Invoices"
},
{
"collapsible": 1,
"fieldname": "invoices",
"fieldtype": "Table",
"label": "Invoices",
- "options": "Subscription Invoice",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Subscription Invoice"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
- "label": "Accounting Dimensions",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "party_type",
@@ -238,9 +191,7 @@
"label": "Party Type",
"options": "DocType",
"reqd": 1,
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "party",
@@ -249,27 +200,21 @@
"label": "Party",
"options": "party_type",
"reqd": 1,
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"depends_on": "eval:doc.party_type === 'Customer'",
"fieldname": "sales_tax_template",
"fieldtype": "Link",
"label": "Sales Taxes and Charges Template",
- "options": "Sales Taxes and Charges Template",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Sales Taxes and Charges Template"
},
{
"depends_on": "eval:doc.party_type === 'Supplier'",
"fieldname": "purchase_tax_template",
"fieldtype": "Link",
"label": "Purchase Taxes and Charges Template",
- "options": "Purchase Taxes and Charges Template",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Purchase Taxes and Charges Template"
},
{
"default": "0",
@@ -277,55 +222,49 @@
"fieldname": "follow_calendar_months",
"fieldtype": "Check",
"label": "Follow Calendar Months",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"default": "0",
"description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
"fieldname": "generate_new_invoices_past_due_date",
"fieldtype": "Check",
- "label": "Generate New Invoices Past Due Date",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Generate New Invoices Past Due Date"
},
{
"fieldname": "end_date",
"fieldtype": "Date",
"label": "Subscription End Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Subscription Start Date",
- "set_only_once": 1,
- "show_days": 1,
- "show_seconds": 1
+ "set_only_once": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
- "options": "Cost Center",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Cost Center"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
- "options": "Company",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Company"
+ },
+ {
+ "default": "1",
+ "fieldname": "submit_invoice",
+ "fieldtype": "Check",
+ "label": "Submit Invoice Automatically"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-02-09 15:44:20.024789",
+ "modified": "2021-04-19 15:24:27.550797",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 826044a..7c4ff73 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -276,7 +276,7 @@
frappe.throw(_('Subscription End Date is mandatory to follow calendar months'))
if billing_info[0]['billing_interval'] != 'Month':
- frappe.throw('Billing Interval in Subscription Plan must be Month to follow calendar months')
+ frappe.throw(_('Billing Interval in Subscription Plan must be Month to follow calendar months'))
def after_insert(self):
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
@@ -383,7 +383,9 @@
invoice.flags.ignore_mandatory = True
invoice.save()
- invoice.submit()
+
+ if self.submit_invoice:
+ invoice.submit()
return invoice
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 09db7fe..5c1cbaa 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -21,7 +21,10 @@
else:
party_type = 'Supplier'
party = inv.supplier
-
+
+ if not party:
+ frappe.throw(_("Please select {0} first").format(party_type))
+
return party_type, party
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
@@ -324,7 +327,7 @@
net_total, ldc.certificate_limit
):
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
-
+
return tds_amount
def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 85bff10..f1717c5 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -171,7 +171,7 @@
else:
allowance = .5
- if abs(debit_credit_diff) >= allowance:
+ if abs(debit_credit_diff) > allowance:
frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.")
.format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff))
diff --git a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
index bd7a126..4c7faf4 100644
--- a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
+++ b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
@@ -1,5 +1,6 @@
{
"attach_print": 0,
+ "channel": "Email",
"condition": "doc.auto_created",
"creation": "2018-04-25 14:19:05.440361",
"days_in_advance": 0,
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 444b40e..db605f7 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -364,7 +364,7 @@
payment_terms_details = frappe.db.sql("""
select
si.name, si.party_account_currency, si.currency, si.conversion_rate,
- ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
+ ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
from `tab{0}` si, `tabPayment Schedule` ps
where
si.name = ps.parent and
@@ -394,7 +394,7 @@
"due_date": d.due_date,
"invoiced": invoiced,
"invoice_grand_total": row.invoiced,
- "payment_term": d.description,
+ "payment_term": d.description or d.payment_term,
"paid": d.paid_amount + d.discounted_amount,
"credit_note": 0.0,
"outstanding": invoiced - d.paid_amount - d.discounted_amount
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py
index 1729abc..287b8a7 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.py
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py
@@ -5,7 +5,8 @@
import frappe
from frappe import _
from frappe.utils import flt, cint
-from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
+from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data,
+ get_filtered_list_for_consolidated_report)
def execute(filters=None):
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
@@ -132,6 +133,10 @@
if filters.get('accumulated_values'):
period_list = [period_list[-1]]
+ # from consolidated financial statement
+ if filters.get('accumulated_in_group_company'):
+ period_list = get_filtered_list_for_consolidated_report(period_list)
+
for period in period_list:
key = period if consolidated else period.key
if asset:
diff --git a/erpnext/accounts/report/billed_items_to_be_received/__init__.py b/erpnext/accounts/report/billed_items_to_be_received/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/__init__.py
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
new file mode 100644
index 0000000..e1fccb6
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports['Billed Items To Be Received'] = {
+ 'filters': [
+ {
+ 'label': __('Company'),
+ 'fieldname': 'company',
+ 'fieldtype': 'Link',
+ 'options': 'Company',
+ 'reqd': 1,
+ 'default': frappe.defaults.get_default('Company')
+ },
+ {
+ 'label': __('As on Date'),
+ 'fieldname': 'posting_date',
+ 'fieldtype': 'Date',
+ 'reqd': 1,
+ 'default': get_today()
+ },
+ {
+ 'label': __('Purchase Invoice'),
+ 'fieldname': 'purchase_invoice',
+ 'fieldtype': 'Link',
+ 'options': 'Purchase Invoice'
+ }
+ ]
+};
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json
new file mode 100644
index 0000000..de09b33
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json
@@ -0,0 +1,39 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-03-30 09:35:38.683028",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-03-31 08:48:30.944429",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Billed Items To Be Received",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "",
+ "ref_doctype": "Purchase Invoice",
+ "report_name": "Billed Items To Be Received",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Auditor"
+ },
+ {
+ "role": "Stock User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
new file mode 100644
index 0000000..2ce5d50
--- /dev/null
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py
@@ -0,0 +1,107 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ data = get_data(filters) or []
+ columns = get_columns()
+
+ return columns, data
+
+def get_data(report_filters):
+ filters = get_report_filters(report_filters)
+ fields = get_report_fields()
+
+ return frappe.get_all('Purchase Invoice',
+ fields= fields, filters=filters)
+
+def get_report_filters(report_filters):
+ filters = [['Purchase Invoice','company','=',report_filters.get('company')],
+ ['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1],
+ ['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]]
+
+ if report_filters.get('purchase_invoice'):
+ filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]])
+
+ return filters
+
+def get_report_fields():
+ fields = []
+ for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']:
+ fields.append('`tabPurchase Invoice`.`{}`'.format(p_field))
+
+ for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']:
+ fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field))
+
+ return fields
+
+def get_columns():
+ return [
+ {
+ 'label': _('Purchase Invoice'),
+ 'fieldname': 'name',
+ 'fieldtype': 'Link',
+ 'options': 'Purchase Invoice',
+ 'width': 170
+ },
+ {
+ 'label': _('Supplier'),
+ 'fieldname': 'supplier',
+ 'fieldtype': 'Link',
+ 'options': 'Supplier',
+ 'width': 120
+ },
+ {
+ 'label': _('Posting Date'),
+ 'fieldname': 'posting_date',
+ 'fieldtype': 'Date',
+ 'width': 100
+ },
+ {
+ 'label': _('Item Code'),
+ 'fieldname': 'item_code',
+ 'fieldtype': 'Link',
+ 'options': 'Item',
+ 'width': 100
+ },
+ {
+ 'label': _('Item Name'),
+ 'fieldname': 'item_name',
+ 'fieldtype': 'Data',
+ 'width': 100
+ },
+ {
+ 'label': _('UOM'),
+ 'fieldname': 'uom',
+ 'fieldtype': 'Link',
+ 'options': 'UOM',
+ 'width': 100
+ },
+ {
+ 'label': _('Invoiced Qty'),
+ 'fieldname': 'qty',
+ 'fieldtype': 'Float',
+ 'width': 100
+ },
+ {
+ 'label': _('Received Qty'),
+ 'fieldname': 'received_qty',
+ 'fieldtype': 'Float',
+ 'width': 100
+ },
+ {
+ 'label': _('Rate'),
+ 'fieldname': 'rate',
+ 'fieldtype': 'Currency',
+ 'width': 100
+ },
+ {
+ 'label': _('Amount'),
+ 'fieldname': 'amount',
+ 'fieldtype': 'Currency',
+ 'width': 100
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py
index cf0946b..3577457 100644
--- a/erpnext/accounts/report/cash_flow/cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/cash_flow.py
@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.utils import cint, cstr
-from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
+from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, get_filtered_list_for_consolidated_report)
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss
from erpnext.accounts.utils import get_fiscal_year
from six import iteritems
@@ -67,9 +67,9 @@
section_data.append(account_data)
add_total_row_account(data, section_data, cash_flow_account['section_footer'],
- period_list, company_currency, summary_data)
+ period_list, company_currency, summary_data, filters)
- add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data)
+ add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters)
columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
chart = get_chart_data(columns, data)
@@ -162,18 +162,26 @@
return start_date
-def add_total_row_account(out, data, label, period_list, currency, summary_data, consolidated = False):
+def add_total_row_account(out, data, label, period_list, currency, summary_data, filters, consolidated=False):
total_row = {
"account_name": "'" + _("{0}").format(label) + "'",
"account": "'" + _("{0}").format(label) + "'",
"currency": currency
}
+
+ summary_data[label] = 0
+
+ # from consolidated financial statement
+ if filters.get('accumulated_in_group_company'):
+ period_list = get_filtered_list_for_consolidated_report(filters, period_list)
+
for row in data:
if row.get("parent_account"):
for period in period_list:
key = period if consolidated else period['key']
total_row.setdefault(key, 0.0)
total_row[key] += row.get(key, 0.0)
+ summary_data[label] += row.get(key)
total_row.setdefault("total", 0.0)
total_row["total"] += row["total"]
@@ -181,7 +189,6 @@
out.append(total_row)
out.append({})
- summary_data[label] = total_row["total"]
def get_report_summary(summary_data, currency):
report_summary = []
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
index 0947922..1363b53 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
@@ -2,118 +2,128 @@
// For license information, please see license.txt
/* eslint-disable */
-frappe.query_reports["Consolidated Financial Statement"] = {
- "filters": [
- {
- "fieldname":"company",
- "label": __("Company"),
- "fieldtype": "Link",
- "options": "Company",
- "default": frappe.defaults.get_user_default("Company"),
- "reqd": 1
- },
- {
- "fieldname":"filter_based_on",
- "label": __("Filter Based On"),
- "fieldtype": "Select",
- "options": ["Fiscal Year", "Date Range"],
- "default": ["Fiscal Year"],
- "reqd": 1,
- on_change: function() {
- let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
- frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
- frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
- frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
- frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
+frappe.require("assets/erpnext/js/financial_statements.js", function() {
+ frappe.query_reports["Consolidated Financial Statement"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"filter_based_on",
+ "label": __("Filter Based On"),
+ "fieldtype": "Select",
+ "options": ["Fiscal Year", "Date Range"],
+ "default": ["Fiscal Year"],
+ "reqd": 1,
+ on_change: function() {
+ let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
+ frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
+ frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
+ frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
+ frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
- frappe.query_report.refresh();
+ frappe.query_report.refresh();
+ }
+ },
+ {
+ "fieldname":"period_start_date",
+ "label": __("Start Date"),
+ "fieldtype": "Date",
+ "hidden": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname":"period_end_date",
+ "label": __("End Date"),
+ "fieldtype": "Date",
+ "hidden": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname":"from_fiscal_year",
+ "label": __("Start Year"),
+ "fieldtype": "Link",
+ "options": "Fiscal Year",
+ "default": frappe.defaults.get_user_default("fiscal_year"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"to_fiscal_year",
+ "label": __("End Year"),
+ "fieldtype": "Link",
+ "options": "Fiscal Year",
+ "default": frappe.defaults.get_user_default("fiscal_year"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"finance_book",
+ "label": __("Finance Book"),
+ "fieldtype": "Link",
+ "options": "Finance Book"
+ },
+ {
+ "fieldname":"report",
+ "label": __("Report"),
+ "fieldtype": "Select",
+ "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
+ "default": "Balance Sheet",
+ "reqd": 1
+ },
+ {
+ "fieldname": "presentation_currency",
+ "label": __("Currency"),
+ "fieldtype": "Select",
+ "options": erpnext.get_presentation_currency_list(),
+ "default": frappe.defaults.get_user_default("Currency")
+ },
+ {
+ "fieldname":"accumulated_in_group_company",
+ "label": __("Accumulated Values in Group Company"),
+ "fieldtype": "Check",
+ "default": 0
+ },
+ {
+ "fieldname": "include_default_book_entries",
+ "label": __("Include Default Book Entries"),
+ "fieldtype": "Check",
+ "default": 1
}
- },
- {
- "fieldname":"period_start_date",
- "label": __("Start Date"),
- "fieldtype": "Date",
- "hidden": 1,
- "reqd": 1
- },
- {
- "fieldname":"period_end_date",
- "label": __("End Date"),
- "fieldtype": "Date",
- "hidden": 1,
- "reqd": 1
- },
- {
- "fieldname":"from_fiscal_year",
- "label": __("Start Year"),
- "fieldtype": "Link",
- "options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
- "reqd": 1
- },
- {
- "fieldname":"to_fiscal_year",
- "label": __("End Year"),
- "fieldtype": "Link",
- "options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
- "reqd": 1
- },
- {
- "fieldname":"finance_book",
- "label": __("Finance Book"),
- "fieldtype": "Link",
- "options": "Finance Book"
- },
- {
- "fieldname":"report",
- "label": __("Report"),
- "fieldtype": "Select",
- "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
- "default": "Balance Sheet",
- "reqd": 1
- },
- {
- "fieldname": "presentation_currency",
- "label": __("Currency"),
- "fieldtype": "Select",
- "options": erpnext.get_presentation_currency_list(),
- "default": frappe.defaults.get_user_default("Currency")
- },
- {
- "fieldname":"accumulated_in_group_company",
- "label": __("Accumulated Values in Group Company"),
- "fieldtype": "Check",
- "default": 0
- },
- {
- "fieldname": "include_default_book_entries",
- "label": __("Include Default Book Entries"),
- "fieldtype": "Check",
- "default": 1
- }
- ],
- "formatter": function(value, row, column, data, default_formatter) {
- value = default_formatter(value, row, column, data);
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ if (data && column.fieldname=="account") {
+ value = data.account_name || value;
+
+ column.link_onclick =
+ "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
+ column.is_tree = true;
+ }
- if (!data.parent_account) {
- value = $(`<span>${value}</span>`);
+ value = default_formatter(value, row, column, data);
- var $value = $(value).css("font-weight", "bold");
+ if (!data.parent_account) {
+ value = $(`<span>${value}</span>`);
- value = $value.wrap("<p></p>").parent().html();
- }
- return value;
- },
- onload: function() {
- let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
+ var $value = $(value).css("font-weight", "bold");
- frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
- var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
- frappe.query_report.set_filter_value({
- period_start_date: fy.year_start_date,
- period_end_date: fy.year_end_date
+ value = $value.wrap("<p></p>").parent().html();
+ }
+ return value;
+ },
+ onload: function() {
+ let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
+
+ frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
+ var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
+ frappe.query_report.set_filter_value({
+ period_start_date: fy.year_start_date,
+ period_end_date: fy.year_end_date
+ });
});
- });
+ }
}
-}
+});
\ No newline at end of file
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index 0c4a422..7793af7 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -94,7 +94,7 @@
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
- report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, True)
+ report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, filters, True)
return data, None, chart, report_summary
@@ -149,9 +149,9 @@
section_data.append(account_data)
add_total_row_account(data, section_data, cash_flow_account['section_footer'],
- companies, company_currency, summary_data, True)
+ companies, company_currency, summary_data, filters, True)
- add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, True)
+ add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, filters, True)
report_summary = get_cash_flow_summary(summary_data, company_currency)
@@ -329,8 +329,9 @@
has_value = False
total = 0
row = frappe._dict({
- "account_name": _(d.account_name),
- "account": _(d.account_name),
+ "account_name": ('%s - %s' %(_(d.account_number), _(d.account_name))
+ if d.account_number else _(d.account_name)),
+ "account": _(d.name),
"parent_account": _(d.parent_account),
"indent": flt(d.indent),
"year_start_date": start_date,
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 14efa1f..d20ddbd 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -119,10 +119,10 @@
def validate_dates(from_date, to_date):
if not from_date or not to_date:
- frappe.throw("From Date and To Date are mandatory")
+ frappe.throw(_("From Date and To Date are mandatory"))
if to_date < from_date:
- frappe.throw("To Date cannot be less than From Date")
+ frappe.throw(_("To Date cannot be less than From Date"))
def get_months(start_date, end_date):
diff = (12 * end_date.year + end_date.month) - (12 * start_date.year + start_date.month)
@@ -522,4 +522,12 @@
"width": 150
})
- return columns
\ No newline at end of file
+ return columns
+
+def get_filtered_list_for_consolidated_report(filters, period_list):
+ filtered_summary_list = []
+ for period in period_list:
+ if period == filters.get('company'):
+ filtered_summary_list.append(period)
+
+ return filtered_summary_list
diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py
index 52f7fe2..cfbd7fd 100644
--- a/erpnext/accounts/report/pos_register/pos_register.py
+++ b/erpnext/accounts/report/pos_register/pos_register.py
@@ -116,22 +116,19 @@
frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method"))
def get_conditions(filters):
- conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format(
- company=filters.get("company"),
- from_date=filters.get("from_date"),
- to_date=filters.get("to_date"))
+ conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s"
if filters.get("pos_profile"):
- conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile"))
+ conditions += " AND pos_profile = %(pos_profile)s"
if filters.get("owner"):
- conditions += " AND owner = %(owner)s".format(owner=filters.get("owner"))
+ conditions += " AND owner = %(owner)s"
if filters.get("customer"):
- conditions += " AND customer = %(customer)s".format(customer=filters.get("customer"))
+ conditions += " AND customer = %(customer)s"
if filters.get("is_return"):
- conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return"))
+ conditions += " AND is_return = %(is_return)s"
if filters.get("mode_of_payment"):
conditions += """
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py
index fe261b3..5d04824 100644
--- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py
+++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py
@@ -5,7 +5,8 @@
import frappe
from frappe import _
from frappe.utils import flt
-from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
+from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data,
+ get_filtered_list_for_consolidated_report)
def execute(filters=None):
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
@@ -33,13 +34,17 @@
chart = get_chart_data(filters, columns, income, expense, net_profit_loss)
currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency")
- report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency)
+ report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters)
return columns, data, None, chart, report_summary
-def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, consolidated=False):
+def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, filters, consolidated=False):
net_income, net_expense, net_profit = 0.0, 0.0, 0.0
+ # from consolidated financial statement
+ if filters.get('accumulated_in_group_company'):
+ period_list = get_filtered_list_for_consolidated_report(filters, period_list)
+
for period in period_list:
key = period if consolidated else period.key
if income:
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 9aff144..8799275 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -195,8 +195,7 @@
# If depreciation is already completed (for double declining balance)
if skip_row: continue
- depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
- d.total_number_of_depreciations, d)
+ depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(d.depreciation_start_date,
@@ -208,7 +207,7 @@
# For first row
if has_pro_rata and n==0:
- depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount,
+ depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
self.available_for_use_date, d.depreciation_start_date)
# For first depr schedule date will be the start date
@@ -220,7 +219,7 @@
to_date = add_months(self.available_for_use_date,
n * cint(d.frequency_of_depreciation))
- depreciation_amount, days, months = get_pro_rata_amt(d,
+ depreciation_amount, days, months = self.get_pro_rata_amt(d,
depreciation_amount, schedule_date, to_date)
monthly_schedule_date = add_months(schedule_date, 1)
@@ -365,24 +364,6 @@
def get_value_after_depreciation(self, idx):
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
- def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
- precision = self.precision("gross_purchase_amount")
-
- if row.depreciation_method in ("Straight Line", "Manual"):
- depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked))
-
- if not depreciation_left:
- frappe.msgprint(_("All the depreciations has been booked"))
- depreciation_amount = flt(row.expected_value_after_useful_life)
- return depreciation_amount
-
- depreciation_amount = (flt(row.value_after_depreciation) -
- flt(row.expected_value_after_useful_life)) / depreciation_left
- else:
- depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision)
-
- return depreciation_amount
-
def validate_expected_value_after_useful_life(self):
for row in self.get('finance_books'):
accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount
@@ -575,6 +556,13 @@
return 100 * (1 - flt(depreciation_rate, float_precision))
+ def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
+ days = date_diff(to_date, from_date)
+ months = month_diff(to_date, from_date)
+ total_days = get_total_days(to_date, row.frequency_of_depreciation)
+
+ return (depreciation_amount * flt(days)) / flt(total_days), days, months
+
def update_maintenance_status():
assets = frappe.get_all(
"Asset", filters={"docstatus": 1, "maintenance_required": 1}
@@ -758,15 +746,20 @@
def is_cwip_accounting_enabled(asset_category):
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
-def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
- days = date_diff(to_date, from_date)
- months = month_diff(to_date, from_date)
- total_days = get_total_days(to_date, row.frequency_of_depreciation)
-
- return (depreciation_amount * flt(days)) / flt(total_days), days, months
-
def get_total_days(date, frequency):
period_start_date = add_months(date,
cint(frequency) * -1)
return date_diff(date, period_start_date)
+
+@erpnext.allow_regional
+def get_depreciation_amount(asset, depreciable_value, row):
+ depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
+
+ if row.depreciation_method in ("Straight Line", "Manual"):
+ depreciation_amount = (flt(row.value_after_depreciation) -
+ flt(row.expected_value_after_useful_life)) / depreciation_left
+ else:
+ depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
+
+ return depreciation_amount
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index a0d7603..30a270c 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -78,7 +78,7 @@
})
doc.set_missing_values()
- self.assertEquals(doc.items[0].is_fixed_asset, 1)
+ self.assertEqual(doc.items[0].is_fixed_asset, 1)
def test_schedule_for_straight_line_method(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
@@ -565,7 +565,7 @@
doc = make_invoice(pr.name)
- self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
+ self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
def test_asset_cwip_toggling_cases(self):
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
@@ -635,6 +635,45 @@
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
+ def test_discounted_wdv_depreciation_rate_for_indian_region(self):
+ # set indian company
+ company_flag = frappe.flags.company
+ frappe.flags.company = "_Test Company"
+
+ pr = make_purchase_receipt(item_code="Macbook Pro",
+ qty=1, rate=8000.0, location="Test Location")
+
+ asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
+ asset = frappe.get_doc('Asset', asset_name)
+ asset.calculate_depreciation = 1
+ asset.available_for_use_date = '2030-06-12'
+ asset.purchase_date = '2030-01-01'
+ asset.append("finance_books", {
+ "expected_value_after_useful_life": 1000,
+ "depreciation_method": "Written Down Value",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 12,
+ "depreciation_start_date": "2030-12-31"
+ })
+ asset.save(ignore_permissions=True)
+
+ self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
+
+ expected_schedules = [
+ ["2030-12-31", 1106.85, 1106.85],
+ ["2031-12-31", 3446.58, 4553.43],
+ ["2032-12-31", 1723.29, 6276.72],
+ ["2033-06-12", 723.28, 7000.00]
+ ]
+
+ schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
+ for d in asset.get("schedules")]
+
+ self.assertEqual(schedules, expected_schedules)
+
+ # reset indian company
+ frappe.flags.company = company_flag
+
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category()
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 3c4f908..aaa98f2 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -187,7 +187,7 @@
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
- self.assertEquals(len(po.get('items')), 2)
+ self.assertEqual(len(po.get('items')), 2)
self.assertEqual(po.status, 'To Receive and Bill')
# ordered qty should increase on row addition
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
@@ -234,7 +234,7 @@
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
- self.assertEquals(len(po.get('items')), 1)
+ self.assertEqual(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill')
# ordered qty should decrease (back to initial) on row deletion
@@ -435,6 +435,35 @@
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 5)
+ def test_purchase_order_invoice_receipt_workflow(self):
+ from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt
+
+ po = create_purchase_order()
+ pi = make_pi_from_po(po.name)
+
+ pi.submit()
+
+ pr = make_purchase_receipt(pi.name)
+ pr.submit()
+
+ pi.load_from_db()
+
+ self.assertEqual(pi.per_received, 100.00)
+ self.assertEqual(pi.items[0].qty, pi.items[0].received_qty)
+
+ po.load_from_db()
+
+ self.assertEqual(po.per_received, 100.00)
+ self.assertEqual(po.per_billed, 100.00)
+
+ pr.cancel()
+
+ pi.load_from_db()
+ pi.cancel()
+
+ po.load_from_db()
+ po.cancel()
+
def test_make_purchase_invoice(self):
po = create_purchase_order(do_not_submit=True)
@@ -645,8 +674,8 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1)
- self.assertEquals(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
- self.assertEquals(bin2.projected_qty, bin1.projected_qty - 10)
+ self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
+ self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10)
# Create stock transfer
rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item",
@@ -661,7 +690,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
+ self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# close PO
po.update_status("Closed")
@@ -669,7 +698,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
+ self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Re-open PO
po.update_status("Submitted")
@@ -677,7 +706,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
+ self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
make_stock_entry(target="_Test Warehouse 1 - _TC", item_code="_Test Item",
qty=40, basic_rate=100)
@@ -694,7 +723,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
+ self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Cancel PR
pr.cancel()
@@ -702,7 +731,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
+ self.assertEqual(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# Make Purchase Invoice
pi = make_pi_from_po(po.name)
@@ -714,7 +743,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
+ self.assertEqual(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Cancel PR
pi.cancel()
@@ -722,7 +751,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
+ self.assertEqual(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# Cancel Stock Entry
se.cancel()
@@ -730,7 +759,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
+ self.assertEqual(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
# Cancel PO
po.reload()
@@ -739,7 +768,7 @@
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
- self.assertEquals(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
+ self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
def test_exploded_items_in_subcontracted(self):
item_code = "_Test Subcontracted FG Item 1"
@@ -753,7 +782,7 @@
exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')])
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
- self.assertEquals(exploded_items, supplied_items)
+ self.assertEqual(exploded_items, supplied_items)
po1 = create_purchase_order(item_code=item_code, qty=1,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0)
@@ -761,7 +790,7 @@
supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items])
bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')])
- self.assertEquals(supplied_items1, bom_items)
+ self.assertEqual(supplied_items1, bom_items)
def test_backflush_based_on_stock_entry(self):
item_code = "_Test Subcontracted FG Item 1"
@@ -811,8 +840,8 @@
transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name])
issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
- self.assertEquals(transferred_items, issued_items)
- self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000)
+ self.assertEqual(transferred_items, issued_items)
+ self.assertEqual(pr.get('items')[0].rm_supp_cost, 2000)
transferred_rm_map = frappe._dict()
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index b686dc0..3f2d339 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -838,9 +838,10 @@
if not self.get("items"):
return
- earliest_schedule_date = min([d.schedule_date for d in self.get("items")])
- if earliest_schedule_date:
- self.schedule_date = earliest_schedule_date
+ if any(d.schedule_date for d in self.get("items")):
+ # Select earliest schedule_date.
+ self.schedule_date = min(d.schedule_date for d in self.get("items")
+ if d.schedule_date is not None)
if self.schedule_date:
for d in self.get('items'):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index bc1ac5e..b31724f 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -292,11 +292,14 @@
cond = """(`tabProject`.customer = %s or
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
- fields = get_fields("Project", ["name"])
+ fields = get_fields("Project", ["name", "project_name"])
+ searchfields = frappe.get_meta("Project").get_search_fields()
+ searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
return frappe.db.sql("""select {fields} from `tabProject`
- where `tabProject`.status not in ("Completed", "Cancelled")
- and {cond} `tabProject`.name like %(txt)s {match_cond}
+ where
+ `tabProject`.status not in ("Completed", "Cancelled")
+ and {cond} {match_cond} {scond}
order by
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
idx desc,
@@ -304,6 +307,7 @@
limit {start}, {page_len}""".format(
fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]),
cond=cond,
+ scond=searchfields,
match_cond=get_match_cond(doctype),
start=start,
page_len=page_len), {
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 4bb6138..ed3aee5 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -100,6 +100,10 @@
["Queued", "eval:self.status == 'Queued'"],
["Failed", "eval:self.status == 'Failed'"],
["Cancelled", "eval:self.docstatus == 2"],
+ ],
+ "Transaction Deletion Record": [
+ ["Draft", None],
+ ["Completed", "eval:self.docstatus == 1"],
]
}
diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py
index f0a05ed..5d5b2e1 100644
--- a/erpnext/erpnext_integrations/connectors/shopify_connection.py
+++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py
@@ -335,13 +335,13 @@
if not last_order_id:
if shopify_settings.sync_based_on == 'Date':
- url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&created_at_min={0}&since_id=0".format(
+ url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format(
get_datetime(shopify_settings.from_date)), shopify_settings)
else:
- url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(
+ url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(
shopify_settings.from_order_id), shopify_settings)
else:
- url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
+ url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
return url
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
index 2948796..d370fbc 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
@@ -19,7 +19,7 @@
mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test")
self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"}))
self.assertTrue(mode_of_payment.name)
- self.assertEquals(mode_of_payment.type, "Phone")
+ self.assertEqual(mode_of_payment.type, "Phone")
def test_processing_of_account_balance(self):
mpesa_doc = create_mpesa_settings(payment_gateway_name="_Account Balance")
@@ -31,11 +31,11 @@
# test integration request creation and successful update of the status on receiving callback response
self.assertTrue(integration_request)
- self.assertEquals(integration_request.status, "Completed")
+ self.assertEqual(integration_request.status, "Completed")
# test formatting of account balance received as string to json with appropriate currency symbol
mpesa_doc.reload()
- self.assertEquals(mpesa_doc.account_balance, dumps({
+ self.assertEqual(mpesa_doc.account_balance, dumps({
"Working Account": {
"current_balance": "Sh 481,000.00",
"available_balance": "Sh 481,000.00",
@@ -60,7 +60,7 @@
pr = pos_invoice.create_payment_request()
# test payment request creation
- self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
+ self.assertEqual(pr.payment_gateway, "Mpesa-Payment")
# submitting payment request creates integration requests with random id
integration_req_ids = frappe.get_all("Integration Request", filters={
@@ -75,12 +75,12 @@
# test integration request creation and successful update of the status on receiving callback response
self.assertTrue(integration_request)
- self.assertEquals(integration_request.status, "Completed")
+ self.assertEqual(integration_request.status, "Completed")
pos_invoice.reload()
integration_request.reload()
- self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
- self.assertEquals(integration_request.status, "Completed")
+ self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
+ self.assertEqual(integration_request.status, "Completed")
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
integration_request.delete()
@@ -104,7 +104,7 @@
pr = pos_invoice.create_payment_request()
# test payment request creation
- self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
+ self.assertEqual(pr.payment_gateway, "Mpesa-Payment")
# submitting payment request creates integration requests with random id
integration_req_ids = frappe.get_all("Integration Request", filters={
@@ -126,12 +126,12 @@
verify_transaction(**callback_response)
# test completion of integration request
integration_request = frappe.get_doc("Integration Request", integration_req_ids[i])
- self.assertEquals(integration_request.status, "Completed")
+ self.assertEqual(integration_request.status, "Completed")
integration_requests.append(integration_request)
# check receipt number once all the integration requests are completed
pos_invoice.reload()
- self.assertEquals(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers))
+ self.assertEqual(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers))
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
[d.delete() for d in integration_requests]
@@ -155,7 +155,7 @@
pr = pos_invoice.create_payment_request()
# test payment request creation
- self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
+ self.assertEqual(pr.payment_gateway, "Mpesa-Payment")
# submitting payment request creates integration requests with random id
integration_req_ids = frappe.get_all("Integration Request", filters={
@@ -175,7 +175,7 @@
verify_transaction(**callback_response)
# test completion of integration request
integration_request = frappe.get_doc("Integration Request", integration_req_ids[0])
- self.assertEquals(integration_request.status, "Completed")
+ self.assertEqual(integration_request.status, "Completed")
# now one request is completed
# second integration request fails
@@ -187,7 +187,7 @@
'name': ['not in', integration_req_ids]
}, pluck="name")
- self.assertEquals(len(new_integration_req_ids), 1)
+ self.assertEqual(len(new_integration_req_ids), 1)
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'")
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
index cbdf906..381c5e5 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
@@ -30,14 +30,14 @@
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
# url = get_shopify_url('admin/webhooks.json', self)
created_webhooks = [d.method for d in self.webhooks]
- url = get_shopify_url('admin/api/2020-04/webhooks.json', self)
+ url = get_shopify_url('admin/api/2021-04/webhooks.json', self)
for method in webhooks:
session = get_request_session()
try:
res = session.post(url, data=json.dumps({
"webhook": {
"topic": method,
- "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'),
+ "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True),
"format": "json"
}
}), headers=get_header(self))
@@ -56,7 +56,7 @@
deleted_webhooks = []
for d in self.webhooks:
- url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self)
+ url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self)
try:
res = session.delete(url, headers=get_header(self))
res.raise_for_status()
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py
index 7866fde..2af57f4 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py
@@ -32,10 +32,12 @@
raise e
def create_customer_address(customer, shopify_customer):
- if not shopify_customer.get("addresses"):
- return
+ addresses = shopify_customer.get("addresses", [])
- for i, address in enumerate(shopify_customer.get("addresses")):
+ if not addresses and "default_address" in shopify_customer:
+ addresses.append(shopify_customer["default_address"])
+
+ for i, address in enumerate(addresses):
address_title, address_type = get_address_title_and_type(customer.customer_name, i)
try :
frappe.get_doc({
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
index f9f0bb3..16efb6c 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
@@ -8,7 +8,7 @@
shopify_variants_attr_list = ["option1", "option2", "option3"]
def sync_item_from_shopify(shopify_settings, item):
- url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
+ url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
session = get_request_session()
try:
diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py
index 362f6cf..3840e78 100644
--- a/erpnext/erpnext_integrations/utils.py
+++ b/erpnext/erpnext_integrations/utils.py
@@ -28,7 +28,7 @@
return innerfn
-def get_webhook_address(connector_name, method, exclude_uri=False):
+def get_webhook_address(connector_name, method, exclude_uri=False, force_https=False):
endpoint = "erpnext.erpnext_integrations.connectors.{0}.{1}".format(connector_name, method)
if exclude_uri:
@@ -39,7 +39,11 @@
except RuntimeError:
url = "http://localhost:8000"
- server_url = '{uri.scheme}://{uri.netloc}/api/method/{endpoint}'.format(uri=urlparse(url), endpoint=endpoint)
+ url_data = urlparse(url)
+ scheme = "https" if force_https else url_data.scheme
+ netloc = url_data.netloc
+
+ server_url = f"{scheme}://{netloc}/api/method/{endpoint}"
return server_url
diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
index fb72073..03e96a4 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
+++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
@@ -17,7 +17,7 @@
procedure_template.disabled = 1
procedure_template.save()
- self.assertEquals(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1)
+ self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1)
def test_consumables(self):
patient, medical_department, practitioner = create_healthcare_docs()
diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.py b/erpnext/healthcare/doctype/lab_test/test_lab_test.py
index 79ab8a4..c9f0029 100644
--- a/erpnext/healthcare/doctype/lab_test/test_lab_test.py
+++ b/erpnext/healthcare/doctype/lab_test/test_lab_test.py
@@ -18,7 +18,7 @@
lab_template.disabled = 1
lab_template.save()
- self.assertEquals(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1)
+ self.assertEqual(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1)
lab_template.reload()
@@ -57,7 +57,7 @@
# sample collection should not be created
lab_test.reload()
- self.assertEquals(lab_test.sample, None)
+ self.assertEqual(lab_test.sample, None)
def test_create_lab_tests_from_sales_invoice(self):
sales_invoice = create_sales_invoice()
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index 2bb8a53..5f2dc48 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -20,13 +20,13 @@
patient, medical_department, practitioner = create_healthcare_docs()
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0)
appointment = create_appointment(patient, practitioner, nowdate())
- self.assertEquals(appointment.status, 'Open')
+ self.assertEqual(appointment.status, 'Open')
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2))
- self.assertEquals(appointment.status, 'Scheduled')
+ self.assertEqual(appointment.status, 'Scheduled')
encounter = create_encounter(appointment)
- self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
+ self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
encounter.cancel()
- self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
+ self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
def test_start_encounter(self):
patient, medical_department, practitioner = create_healthcare_docs()
diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
index 7fb159d..d079bed 100644
--- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
@@ -18,24 +18,24 @@
def test_status(self):
plan = create_therapy_plan()
- self.assertEquals(plan.status, 'Not Started')
+ self.assertEqual(plan.status, 'Not Started')
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company')
frappe.get_doc(session).submit()
- self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress')
+ self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress')
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company')
frappe.get_doc(session).submit()
- self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
+ self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
patient, medical_department, practitioner = create_healthcare_docs()
appointment = create_appointment(patient, practitioner, nowdate())
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name)
session = frappe.get_doc(session)
session.submit()
- self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
+ self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
session.cancel()
- self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
+ self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
def test_therapy_plan_from_template(self):
patient = create_patient()
@@ -49,7 +49,7 @@
si.save()
therapy_plan_template_amt = frappe.db.get_value('Therapy Plan Template', template, 'total_amount')
- self.assertEquals(si.items[0].amount, therapy_plan_template_amt)
+ self.assertEqual(si.items[0].amount, therapy_plan_template_amt)
def create_therapy_plan(template=None):
diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
index 03a1be8..21f6369 100644
--- a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
+++ b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
@@ -13,7 +13,7 @@
therapy_type.disabled = 1
therapy_type.save()
- self.assertEquals(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1)
+ self.assertEqual(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1)
def create_therapy_type():
exercise = create_exercise_type()
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index bb6cd8b..9d1ce9b 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -426,7 +426,8 @@
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
- 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
+ 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
+ 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
},
'United Arab Emirates': {
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
index 74ce301..3b99c57 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
@@ -68,19 +68,19 @@
filters = dict(transaction_name=compensatory_leave_request.leave_allocation)
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters)
- self.assertEquals(len(leave_ledger_entry), 1)
- self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, 1)
+ self.assertEqual(len(leave_ledger_entry), 1)
+ self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, 1)
# check reverse leave ledger entry on cancellation
compensatory_leave_request.cancel()
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc')
- self.assertEquals(len(leave_ledger_entry), 2)
- self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, -1)
+ self.assertEqual(len(leave_ledger_entry), 2)
+ self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, -1)
def get_compensatory_leave_request(employee, leave_date=today()):
prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request',
diff --git a/erpnext/hr/doctype/designation/designation.json b/erpnext/hr/doctype/designation/designation.json
index 4c3888b..bab6b90 100644
--- a/erpnext/hr/doctype/designation/designation.json
+++ b/erpnext/hr/doctype/designation/designation.json
@@ -182,6 +182,10 @@
"share": 1,
"submit": 0,
"write": 1
+ },
+ {
+ "read": 1,
+ "role": "Sales User"
}
],
"quick_entry": 1,
@@ -191,4 +195,4 @@
"track_changes": 0,
"track_seen": 0,
"track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js
index 5037ceb..fa4b06a 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.js
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.js
@@ -34,7 +34,7 @@
};
});
- frm.set_query('salary_component', function(doc) {
+ frm.set_query('salary_component', function() {
return {
filters: {
"type": "Deduction"
@@ -44,48 +44,49 @@
},
refresh: function(frm) {
- if (frm.doc.docstatus===1
- && (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount))
- && frappe.model.can_create("Payment Entry")) {
+ if (frm.doc.docstatus === 1 &&
+ (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) &&
+ frappe.model.can_create("Payment Entry")) {
frm.add_custom_button(__('Payment'),
- function() { frm.events.make_payment_entry(frm); }, __('Create'));
- }
- else if (
- frm.doc.docstatus === 1
- && flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount)
- && frappe.model.can_create("Expense Claim")
+ function () {
+ frm.events.make_payment_entry(frm);
+ }, __('Create'));
+ } else if (
+ frm.doc.docstatus === 1 &&
+ flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) &&
+ frappe.model.can_create("Expense Claim")
) {
frm.add_custom_button(
__("Expense Claim"),
- function() {
+ function () {
frm.events.make_expense_claim(frm);
},
__('Create')
);
}
- if (frm.doc.docstatus === 1
- && (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
+ if (frm.doc.docstatus === 1 &&
+ (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
- if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")){
- frm.add_custom_button(__("Return"), function() {
+ if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")) {
+ frm.add_custom_button(__("Return"), function() {
frm.trigger('make_return_entry');
}, __('Create'));
- }else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){
- frm.add_custom_button(__("Deduction from salary"), function() {
+ } else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) {
+ frm.add_custom_button(__("Deduction from salary"), function() {
frm.events.make_deduction_via_additional_salary(frm);
}, __('Create'));
}
}
},
- make_deduction_via_additional_salary: function(frm){
+ make_deduction_via_additional_salary: function(frm) {
frappe.call({
method: "erpnext.hr.doctype.employee_advance.employee_advance.create_return_through_additional_salary",
args: {
doc: frm.doc
},
- callback: function (r){
+ callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
@@ -94,7 +95,7 @@
make_payment_entry: function(frm) {
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
- if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
+ if (frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
method = "erpnext.hr.doctype.employee_advance.employee_advance.make_bank_entry";
}
return frappe.call({
@@ -148,11 +149,11 @@
});
},
- employee: function (frm) {
+ employee: function(frm) {
if (frm.doc.employee) {
frappe.run_serially([
- () => frm.trigger('get_employee_currency'),
- () => frm.trigger('get_pending_amount')
+ () => frm.trigger('get_employee_currency'),
+ () => frm.trigger('get_pending_amount')
]);
}
},
@@ -199,7 +200,7 @@
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
- frm.set_df_property("exchange_rate", "description", "" );
+ frm.set_df_property("exchange_rate", "description", "");
}
frm.refresh_fields();
}
@@ -215,8 +216,8 @@
callback: function(r) {
frm.set_value("exchange_rate", flt(r.message));
frm.set_df_property('exchange_rate', 'hidden', 0);
- frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
- + " = [?] " + company_currency);
+ frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
+ " = [?] " + company_currency);
}
});
}
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
index c3b4a3a..2f493e2 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
@@ -4,10 +4,10 @@
def get_data():
return {
'fieldname': 'employee_advance',
- 'non_standard_fieldnames': {
- 'Payment Entry': 'reference_name',
- 'Journal Entry': 'reference_name'
- },
+ 'non_standard_fieldnames': {
+ 'Payment Entry': 'reference_name',
+ 'Journal Entry': 'reference_name'
+ },
'transactions': [
{
'items': ['Expense Claim']
diff --git a/erpnext/hr/doctype/employee_referral/__init__.py b/erpnext/hr/doctype/employee_referral/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/__init__.py
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.js b/erpnext/hr/doctype/employee_referral/employee_referral.js
new file mode 100644
index 0000000..9c99bbb
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("Employee Referral", {
+ refresh: function(frm) {
+ if (frm.doc.docstatus === 1 && frm.doc.status === "Pending") {
+ frm.add_custom_button(__("Reject Employee Referral"), function() {
+ frappe.confirm(
+ __("Are you sure you want to reject the Employee Referral?"),
+ function() {
+ frm.doc.status = "Rejected";
+ frm.dirty();
+ frm.save_or_update();
+ },
+ function() {
+ window.close();
+ }
+ );
+ });
+
+ frm.add_custom_button(__("Create Job Applicant"), function() {
+ frm.events.create_job_applicant(frm);
+ }).addClass("btn-primary");
+ }
+
+ // To check whether Payment is done or not
+ if (frm.doc.docstatus === 1 && frm.doc.status === "Accepted") {
+ frappe.db.get_list("Additional Salary", {
+ filters: {
+ ref_docname: cur_frm.doc.name,
+ docstatus: 1
+ },
+ fields: ["count(name) as additional_salary_count"]
+ }).then((data) => {
+
+ let additional_salary_count = data[0].additional_salary_count;
+
+ if (frm.doc.is_applicable_for_referral_bonus && !additional_salary_count) {
+ frm.add_custom_button(__("Create Additional Salary"), function() {
+ frm.events.create_additional_salary(frm);
+ }).addClass("btn-primary");
+ }
+ });
+ }
+
+
+
+ },
+ create_job_applicant: function(frm) {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.hr.doctype.employee_referral.employee_referral.create_job_applicant",
+ frm: frm
+ });
+ },
+
+ create_additional_salary: function(frm) {
+ frappe.call({
+ method: "erpnext.hr.doctype.employee_referral.employee_referral.create_additional_salary",
+ args: {
+ doc: frm.doc
+ },
+ callback: function (r) {
+ var doclist = frappe.model.sync(r.message);
+ frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
+ }
+ });
+ },
+});
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.json b/erpnext/hr/doctype/employee_referral/employee_referral.json
new file mode 100644
index 0000000..bfd404b
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.json
@@ -0,0 +1,294 @@
+{
+ "actions": [],
+ "autoname": "format:HR-REF-{####}",
+ "creation": "2021-03-23 14:54:45.047051",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "first_name",
+ "last_name",
+ "full_name",
+ "email",
+ "contact_no",
+ "resume",
+ "resume_link",
+ "column_break_6",
+ "date",
+ "status",
+ "for_designation",
+ "current_employer",
+ "current_job_title",
+ "referrer_details_section",
+ "referrer",
+ "referrer_name",
+ "column_break_14",
+ "is_applicable_for_referral_bonus",
+ "referral_payment_status",
+ "department",
+ "additional_information_section",
+ "qualification_reason",
+ "work_references",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "first_name",
+ "fieldtype": "Data",
+ "label": "First Name ",
+ "reqd": 1
+ },
+ {
+ "fieldname": "last_name",
+ "fieldtype": "Data",
+ "label": "Last Name",
+ "reqd": 1
+ },
+ {
+ "fieldname": "full_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Full Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "contact_no",
+ "fieldtype": "Data",
+ "in_standard_filter": 1,
+ "label": "Contact No.",
+ "options": "Phone"
+ },
+ {
+ "fieldname": "current_employer",
+ "fieldtype": "Data",
+ "label": "Current Employer "
+ },
+ {
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "in_standard_filter": 1,
+ "label": "Date",
+ "reqd": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_standard_filter": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "options": "Pending\nIn Process\nAccepted\nRejected",
+ "permlevel": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "current_job_title",
+ "fieldtype": "Data",
+ "label": "Current Job Title"
+ },
+ {
+ "fieldname": "resume",
+ "fieldtype": "Attach",
+ "label": "Resume"
+ },
+ {
+ "fieldname": "referrer_details_section",
+ "fieldtype": "Section Break",
+ "label": "Referrer Details"
+ },
+ {
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "label": "Department",
+ "options": "Department",
+ "read_only": 1
+ },
+ {
+ "fieldname": "additional_information_section",
+ "fieldtype": "Section Break",
+ "label": "Additional Information "
+ },
+ {
+ "fieldname": "work_references",
+ "fieldtype": "Text Editor",
+ "label": "Work References"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Employee Referral",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "for_designation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "For Designation ",
+ "options": "Designation",
+ "reqd": 1
+ },
+ {
+ "fieldname": "email",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Email",
+ "options": "Email",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "is_applicable_for_referral_bonus",
+ "fieldtype": "Check",
+ "label": "Is Applicable for Referral Bonus"
+ },
+ {
+ "fieldname": "qualification_reason",
+ "fieldtype": "Text Editor",
+ "label": "Why is this Candidate Qualified for this Position?"
+ },
+ {
+ "fieldname": "referrer",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Referrer",
+ "options": "Employee",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "referrer.employee_name",
+ "fieldname": "referrer_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Referrer Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "resume_link",
+ "fieldtype": "Data",
+ "label": "Resume Link"
+ },
+ {
+ "fieldname": "referral_payment_status",
+ "fieldtype": "Select",
+ "label": "Referral Bonus Payment Status",
+ "options": "\nUnpaid\nPaid",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-04-26 21:21:38.094086",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Referral",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "create": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Employee",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "amend": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "HR User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Employee",
+ "share": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "full_name"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py
new file mode 100644
index 0000000..45d6872
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import get_link_to_form
+from frappe.model.document import Document
+
+class EmployeeReferral(Document):
+ def validate(self):
+ self.set_full_name()
+ self.set_referral_bonus_payment_status()
+
+ def set_full_name(self):
+ self.full_name = " ".join(filter(None, [self.first_name, self.last_name]))
+
+ def set_referral_bonus_payment_status(self):
+ if not self.is_applicable_for_referral_bonus:
+ self.referral_payment_status = ""
+ else:
+ if not self.referral_payment_status:
+ self.referral_payment_status = "Unpaid"
+
+
+@frappe.whitelist()
+def create_job_applicant(source_name, target_doc=None):
+ emp_ref = frappe.get_doc("Employee Referral", source_name)
+ #just for Api call if some set status apart from default Status
+ status = emp_ref.status
+ if emp_ref.status in ["Pending", "In process"]:
+ status = "Open"
+
+ job_applicant = frappe.new_doc("Job Applicant")
+ job_applicant.employee_referral = emp_ref.name
+ job_applicant.status = status
+ job_applicant.applicant_name = emp_ref.full_name
+ job_applicant.email_id = emp_ref.email
+ job_applicant.phone_number = emp_ref.contact_no
+ job_applicant.resume_attachment = emp_ref.resume
+ job_applicant.resume_link = emp_ref.resume_link
+ job_applicant.save()
+
+ frappe.msgprint(_("Job Applicant {0} created successfully.").format(
+ get_link_to_form("Job Applicant", job_applicant.name)),
+ title=_("Success"), indicator="green")
+
+ emp_ref.db_set("status", "In Process")
+
+ return job_applicant
+
+
+@frappe.whitelist()
+def create_additional_salary(doc):
+ import json
+ from six import string_types
+
+ if isinstance(doc, string_types):
+ doc = frappe._dict(json.loads(doc))
+
+ if not frappe.db.exists("Additional Salary", {"ref_docname": doc.name}):
+ additional_salary = frappe.new_doc("Additional Salary")
+ additional_salary.employee = doc.referrer
+ additional_salary.company = frappe.db.get_value("Employee", doc.referrer, "company")
+ additional_salary.overwrite_salary_structure_amount = 0
+ additional_salary.ref_doctype = doc.doctype
+ additional_salary.ref_docname = doc.name
+
+ return additional_salary
+
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
new file mode 100644
index 0000000..afa2a1f
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
@@ -0,0 +1,15 @@
+from __future__ import unicode_literals
+
+def get_data():
+ return {
+ 'fieldname': 'employee_referral',
+ 'non_standard_fieldnames': {
+ 'Additional Salary': 'ref_docname'
+ },
+ 'transactions': [
+ {
+ 'items': ['Job Applicant', 'Additional Salary']
+ },
+
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_list.js b/erpnext/hr/doctype/employee_referral/employee_referral_list.js
new file mode 100644
index 0000000..7533ab6
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/employee_referral_list.js
@@ -0,0 +1,14 @@
+frappe.listview_settings['Employee Referral'] = {
+ add_fields: ["status"],
+ get_indicator: function (doc) {
+ if (doc.status == "Pending") {
+ return [__(doc.status), "grey", "status,=," + doc.status];
+ } else if (doc.status == "In Process") {
+ return [__(doc.status), "orange", "status,=," + doc.status];
+ } else if (doc.status == "Accepted") {
+ return [__(doc.status), "green", "status,=," + doc.status];
+ } else if (doc.status == "Rejected") {
+ return [__(doc.status), "red", "status,=," + doc.status];
+ }
+ },
+};
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
new file mode 100644
index 0000000..a674f39
--- /dev/null
+++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+from frappe.utils import today
+from erpnext.hr.doctype.designation.test_designation import create_designation
+from erpnext.hr.doctype.employee_referral.employee_referral import create_job_applicant, create_additional_salary
+from erpnext.hr.doctype.employee.test_employee import make_employee
+import unittest
+
+class TestEmployeeReferral(unittest.TestCase):
+ def test_workflow_and_status_sync(self):
+ emp_ref = create_employee_referral()
+
+ #Check Initial status
+ self.assertTrue(emp_ref.status, "Pending")
+
+ job_applicant = create_job_applicant(emp_ref.name)
+
+
+ #Check status sync
+ emp_ref.reload()
+ self.assertTrue(emp_ref.status, "In Process")
+
+ job_applicant.reload()
+ job_applicant.status = "Rejected"
+ job_applicant.save()
+
+ emp_ref.reload()
+ self.assertTrue(emp_ref.status, "Rejected")
+
+ job_applicant.reload()
+ job_applicant.status = "Accepted"
+ job_applicant.save()
+
+ emp_ref.reload()
+ self.assertTrue(emp_ref.status, "Accepted")
+
+
+ # Check for Referral reference in additional salary
+
+ add_sal = create_additional_salary(emp_ref)
+ self.assertTrue(add_sal.ref_docname, emp_ref.name)
+
+
+def create_employee_referral():
+ emp_ref = frappe.new_doc("Employee Referral")
+ emp_ref.first_name = "Mahesh"
+ emp_ref.last_name = "Singh"
+ emp_ref.email = "a@b.c"
+ emp_ref.date = today()
+ emp_ref.for_designation = create_designation().name
+ emp_ref.referrer = make_employee("testassetmovemp@example.com", company="_Test Company")
+ emp_ref.is_applicable_for_employee_referral_compensation = 1
+ emp_ref.save()
+ emp_ref.submit()
+
+ return emp_ref
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json
index f44d830..7af20988 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.json
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.json
@@ -1,626 +1,177 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "HR-EMP-SEP-.YYYY.-.#####",
- "beta": 0,
- "creation": "2018-05-10 02:29:16.740490",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "HR-EMP-SEP-.YYYY.-.#####",
+ "creation": "2018-05-10 02:29:16.740490",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "employee",
+ "employee_name",
+ "department",
+ "designation",
+ "employee_grade",
+ "column_break_7",
+ "company",
+ "boarding_status",
+ "resignation_letter_date",
+ "project",
+ "table_for_activity",
+ "employee_separation_template",
+ "activities",
+ "notify_users_by_email",
+ "section_break_14",
+ "exit_interview",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Employee",
- "length": 0,
- "no_copy": 0,
- "options": "Employee",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.employee_name",
- "fieldname": "employee_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": "Employee Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.resignation_letter_date",
- "fieldname": "resignation_letter_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Resignation Letter Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "boarding_status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 0,
- "options": "\nPending\nIn Process\nCompleted",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "label": "Employee",
+ "options": "Employee",
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_bulk_edit": 0,
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_in_quick_entry": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "notify_users_by_email",
- "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": "Notify users by email",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_7",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "employee_separation_template",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Employee Separation Template",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Separation Template",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.company",
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fetch_from": "employee.employee_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Employee Name",
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "project",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Project",
- "length": 0,
- "no_copy": 0,
- "options": "Project",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "employee.resignation_letter_date",
+ "fieldname": "resignation_letter_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Resignation Letter Date",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.department",
- "fieldname": "department",
- "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": "Department",
- "length": 0,
- "no_copy": 0,
- "options": "Department",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "fieldname": "boarding_status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "\nPending\nIn Process\nCompleted",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.designation",
- "fieldname": "designation",
- "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": "Designation",
- "length": 0,
- "no_copy": 0,
- "options": "Designation",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "default": "0",
+ "fieldname": "notify_users_by_email",
+ "fieldtype": "Check",
+ "label": "Notify users by email"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "employee.grade",
- "fieldname": "employee_grade",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Employee Grade",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Grade",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "table_for_activity",
- "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": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "employee_separation_template",
+ "fieldtype": "Link",
+ "label": "Employee Separation Template",
+ "options": "Employee Separation Template"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "activities",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Activities",
- "length": 0,
- "no_copy": 0,
- "options": "Employee Boarding Activity",
- "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
- },
+ "fetch_from": "employee.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_14",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "exit_interview",
- "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": "Exit Interview Summary",
- "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
- },
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Department",
+ "options": "Department",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Employee Separation",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fetch_from": "employee.designation",
+ "fieldname": "designation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Designation",
+ "options": "Designation",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "employee.grade",
+ "fieldname": "employee_grade",
+ "fieldtype": "Link",
+ "label": "Employee Grade",
+ "options": "Employee Grade",
+ "read_only": 1
+ },
+ {
+ "fieldname": "table_for_activity",
+ "fieldtype": "Section Break",
+ "label": "Separation Activities"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "activities",
+ "fieldtype": "Table",
+ "label": "Activities",
+ "options": "Employee Boarding Activity"
+ },
+ {
+ "fieldname": "section_break_14",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "exit_interview",
+ "fieldtype": "Text Editor",
+ "label": "Exit Interview Summary"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Employee Separation",
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-08-03 16:15:39.025898",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Employee Separation",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-04-28 15:58:36.020196",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Separation",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "employee_name",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "employee_name",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
index 2fa114d..713fcf5 100644
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
@@ -18,7 +18,7 @@
'activity_name': 'Deactivate Employee',
'role': 'HR User'
})
- separation.status = 'Pending'
+ separation.boarding_status = 'Pending'
separation.insert()
separation.submit()
self.assertEqual(separation.docstatus, 1)
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json
index e3e6e80..a268c15 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.json
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.json
@@ -14,6 +14,7 @@
"column_break_5",
"expense_approver",
"approval_status",
+ "delivery_trip",
"is_paid",
"expense_details",
"expenses",
@@ -365,13 +366,20 @@
"label": "Total Taxes and Charges",
"options": "Company:company:default_currency",
"read_only": 1
+ },
+ {
+ "depends_on": "eval: doc.delivery_trip",
+ "fieldname": "delivery_trip",
+ "fieldtype": "Link",
+ "label": "Delivery Trip",
+ "options": "Delivery Trip"
}
],
"icon": "fa fa-money",
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-09-18 17:26:09.703215",
+ "modified": "2021-05-04 05:35:12.040199",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 3f22ca2..578eccf 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -88,9 +88,9 @@
])
for gle in gl_entries:
- self.assertEquals(expected_values[gle.account][0], gle.account)
- self.assertEquals(expected_values[gle.account][1], gle.debit)
- self.assertEquals(expected_values[gle.account][2], gle.credit)
+ self.assertEqual(expected_values[gle.account][0], gle.account)
+ self.assertEqual(expected_values[gle.account][1], gle.debit)
+ self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_rejected_expense_claim(self):
payable_account = get_payable_account(company_name)
@@ -104,11 +104,11 @@
})
expense_claim.submit()
- self.assertEquals(expense_claim.status, 'Rejected')
- self.assertEquals(expense_claim.total_sanctioned_amount, 0.0)
+ self.assertEqual(expense_claim.status, 'Rejected')
+ self.assertEqual(expense_claim.total_sanctioned_amount, 0.0)
gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
- self.assertEquals(len(gl_entry), 0)
+ self.assertEqual(len(gl_entry), 0)
def test_expense_approver_perms(self):
user = "test_approver_perm_emp@example.com"
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json
index 1360fd1..bcea5f5 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.json
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.json
@@ -18,6 +18,7 @@
"job_title",
"source",
"source_name",
+ "employee_referral",
"applicant_rating",
"section_break_6",
"notes",
@@ -152,13 +153,20 @@
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
+ },
+ {
+ "fieldname": "employee_referral",
+ "fieldtype": "Link",
+ "label": "Employee Referral",
+ "options": "Employee Referral",
+ "read_only": 1
}
],
"icon": "fa fa-user",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-09-18 12:39:02.557563",
+ "modified": "2021-03-24 15:51:11.117517",
"modified_by": "Administrator",
"module": "HR",
"name": "Job Applicant",
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index a6aef04..0594ba3 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -28,10 +28,21 @@
if self.email_id:
validate_email_address(self.email_id, True)
+ if self.employee_referral:
+ self.set_status_for_employee_referral()
+
if not self.applicant_name and self.email_id:
guess = self.email_id.split('@')[0]
self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')])
+ def set_status_for_employee_referral(self):
+ emp_ref = frappe.get_doc("Employee Referral", self.employee_referral)
+ if self.status in ["Open", "Replied", "Hold"]:
+ emp_ref.db_set("status", "In Process")
+ elif self.status in ["Accepted", "Rejected"]:
+ emp_ref.db_set("status", self.status)
+
+
def check_email_id_is_unique(self):
if self.email_id:
names = frappe.db.sql_list("""select name from `tabJob Applicant`
diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py
index 690a692..b3e1dc8 100644
--- a/erpnext/hr/doctype/job_offer/test_job_offer.py
+++ b/erpnext/hr/doctype/job_offer/test_job_offer.py
@@ -35,13 +35,13 @@
job_offer = create_job_offer(job_applicant=job_applicant.name)
job_offer.submit()
job_applicant.reload()
- self.assertEquals(job_applicant.status, "Accepted")
+ self.assertEqual(job_applicant.status, "Accepted")
# status update after rejection
job_offer.status = "Rejected"
job_offer.submit()
job_applicant.reload()
- self.assertEquals(job_applicant.status, "Rejected")
+ self.assertEqual(job_applicant.status, "Rejected")
def create_job_offer(**args):
args = frappe._dict(args)
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index 0b71036..6e7ae87 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -96,7 +96,7 @@
carry_forward=1)
leave_allocation_1.submit()
- self.assertEquals(leave_allocation_1.unused_leaves, 10)
+ self.assertEqual(leave_allocation_1.unused_leaves, 10)
leave_allocation_1.cancel()
@@ -108,7 +108,7 @@
new_leaves_allocated=25)
leave_allocation_2.submit()
- self.assertEquals(leave_allocation_2.unused_leaves, 5)
+ self.assertEqual(leave_allocation_2.unused_leaves, 5)
def test_carry_forward_leaves_expiry(self):
frappe.db.sql("delete from `tabLeave Allocation`")
@@ -145,7 +145,7 @@
to_date=add_months(nowdate(), 12))
leave_allocation_1.submit()
- self.assertEquals(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated)
+ self.assertEqual(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated)
def test_creation_of_leave_ledger_entry_on_submit(self):
frappe.db.sql("delete from `tabLeave Allocation`")
@@ -155,10 +155,10 @@
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name))
- self.assertEquals(len(leave_ledger_entry), 1)
- self.assertEquals(leave_ledger_entry[0].employee, leave_allocation.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, leave_allocation.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated)
+ self.assertEqual(len(leave_ledger_entry), 1)
+ self.assertEqual(leave_ledger_entry[0].employee, leave_allocation.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, leave_allocation.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated)
# check if leave ledger entry is deleted on cancellation
leave_allocation.cancel()
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index b54c971..a4a96b8 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -16,36 +16,36 @@
test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
_test_records = [
- {
- "company": "_Test Company",
- "doctype": "Leave Application",
- "employee": "_T-Employee-00001",
- "from_date": "2013-05-01",
- "description": "_Test Reason",
- "leave_type": "_Test Leave Type",
- "posting_date": "2013-01-02",
- "to_date": "2013-05-05"
- },
- {
- "company": "_Test Company",
- "doctype": "Leave Application",
- "employee": "_T-Employee-00002",
- "from_date": "2013-05-01",
- "description": "_Test Reason",
- "leave_type": "_Test Leave Type",
- "posting_date": "2013-01-02",
- "to_date": "2013-05-05"
- },
- {
- "company": "_Test Company",
- "doctype": "Leave Application",
- "employee": "_T-Employee-00001",
- "from_date": "2013-01-15",
- "description": "_Test Reason",
- "leave_type": "_Test Leave Type LWP",
- "posting_date": "2013-01-02",
- "to_date": "2013-01-15"
- }
+ {
+ "company": "_Test Company",
+ "doctype": "Leave Application",
+ "employee": "_T-Employee-00001",
+ "from_date": "2013-05-01",
+ "description": "_Test Reason",
+ "leave_type": "_Test Leave Type",
+ "posting_date": "2013-01-02",
+ "to_date": "2013-05-05"
+ },
+ {
+ "company": "_Test Company",
+ "doctype": "Leave Application",
+ "employee": "_T-Employee-00002",
+ "from_date": "2013-05-01",
+ "description": "_Test Reason",
+ "leave_type": "_Test Leave Type",
+ "posting_date": "2013-01-02",
+ "to_date": "2013-05-05"
+ },
+ {
+ "company": "_Test Company",
+ "doctype": "Leave Application",
+ "employee": "_T-Employee-00001",
+ "from_date": "2013-01-15",
+ "description": "_Test Reason",
+ "leave_type": "_Test Leave Type LWP",
+ "posting_date": "2013-01-02",
+ "to_date": "2013-01-15"
+ }
]
@@ -516,9 +516,9 @@
leave_application.submit()
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name))
- self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1)
+ self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1)
# check if leave ledger entry is deleted on cancellation
leave_application.cancel()
@@ -549,11 +549,11 @@
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name))
- self.assertEquals(len(leave_ledger_entry), 2)
- self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, -9)
- self.assertEquals(leave_ledger_entry[1].leaves, -2)
+ self.assertEqual(len(leave_ledger_entry), 2)
+ self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, -9)
+ self.assertEqual(leave_ledger_entry[1].leaves, -2)
def test_leave_application_creation_after_expiry(self):
# test leave balance for carry forwarded allocation
@@ -566,7 +566,7 @@
create_carry_forwarded_allocation(employee, leave_type)
- self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
+ self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
def test_leave_approver_perms(self):
employee = get_employee()
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
index aafc964..e0ffa5d 100644
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
@@ -88,10 +88,10 @@
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name))
- self.assertEquals(len(leave_ledger_entry), 1)
- self.assertEquals(leave_ledger_entry[0].employee, leave_encashment.employee)
- self.assertEquals(leave_ledger_entry[0].leave_type, leave_encashment.leave_type)
- self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1)
+ self.assertEqual(len(leave_ledger_entry), 1)
+ self.assertEqual(leave_ledger_entry[0].employee, leave_encashment.employee)
+ self.assertEqual(leave_ledger_entry[0].leave_type, leave_encashment.leave_type)
+ self.assertEqual(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1)
# check if leave ledger entry is deleted on cancellation
diff --git a/erpnext/hr/notification/training_feedback/training_feedback.json b/erpnext/hr/notification/training_feedback/training_feedback.json
index 2cc064f..92b68a9 100644
--- a/erpnext/hr/notification/training_feedback/training_feedback.json
+++ b/erpnext/hr/notification/training_feedback/training_feedback.json
@@ -1,5 +1,6 @@
{
"attach_print": 0,
+ "channel": "Email",
"creation": "2017-08-11 03:17:11.769210",
"days_in_advance": 0,
"docstatus": 0,
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 190eb4f..2540b3d 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -32,13 +32,15 @@
project_name += self.job_applicant
else:
project_name += self.employee
+
project = frappe.get_doc({
"doctype": "Project",
"project_name": project_name,
"expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date,
"department": self.department,
"company": self.company
- }).insert(ignore_permissions=True)
+ }).insert(ignore_permissions=True, ignore_mandatory=True)
+
self.db_set("project", project.name)
self.db_set("boarding_status", "Pending")
self.reload()
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index f4b56a0..c5201c2 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -521,6 +521,15 @@
"type": "Link"
},
{
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Referral",
+ "link_to": "Employee Referral",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
@@ -814,7 +823,7 @@
"type": "Link"
}
],
- "modified": "2021-03-24 17:35:21.483297",
+ "modified": "2021-04-26 13:36:15.413819",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 20b44a1..230475f 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -71,7 +71,6 @@
frappe.throw(_("Repay From Salary can be selected only for term loans"))
def make_repayment_schedule(self):
-
if not self.repayment_start_date:
frappe.throw(_("Repayment Start Date is mandatory for term loans"))
@@ -79,10 +78,9 @@
payment_date = self.repayment_start_date
balance_amount = self.loan_amount
while(balance_amount > 0):
- interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100))
+ interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12*100))
principal_amount = self.monthly_repayment_amount - interest_amount
- balance_amount = rounded(balance_amount + interest_amount - self.monthly_repayment_amount)
-
+ balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount)
if balance_amount < 0:
principal_amount += balance_amount
balance_amount = 0.0
@@ -196,7 +194,8 @@
posting_date = getdate()
amounts = calculate_amounts(loan, posting_date)
- pending_amount = amounts['payable_amount'] + amounts['unaccrued_interest']
+ pending_amount = amounts['pending_principal_amount'] + amounts['unaccrued_interest'] + \
+ amounts['interest_amount'] + amounts['penalty_amount']
loan_type = frappe.get_value('Loan', loan, 'loan_type')
write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount')
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 6f8da31..fa4707c 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -55,26 +55,26 @@
def test_loan(self):
loan = frappe.get_doc("Loan", {"applicant":self.applicant1})
- self.assertEquals(loan.monthly_repayment_amount, 15052)
- self.assertEquals(loan.total_interest_payable, 21034)
- self.assertEquals(loan.total_payment, 301034)
+ self.assertEqual(loan.monthly_repayment_amount, 15052)
+ self.assertEqual(flt(loan.total_interest_payable, 0), 21034)
+ self.assertEqual(flt(loan.total_payment, 0), 301034)
schedule = loan.repayment_schedule
self.assertEqual(len(schedule), 20)
- for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227079], [19, 14941, 105, 0], [17, 14740, 312, 29785]]:
- self.assertEqual(schedule[idx].principal_amount, principal_amount)
- self.assertEqual(schedule[idx].interest_amount, interest_amount)
- self.assertEqual(schedule[idx].balance_loan_amount, balance_loan_amount)
+ for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227080], [19, 14941, 105, 0], [17, 14740, 312, 29785]]:
+ self.assertEqual(flt(schedule[idx].principal_amount, 0), principal_amount)
+ self.assertEqual(flt(schedule[idx].interest_amount, 0), interest_amount)
+ self.assertEqual(flt(schedule[idx].balance_loan_amount, 0), balance_loan_amount)
loan.repayment_method = "Repay Fixed Amount per Period"
loan.monthly_repayment_amount = 14000
loan.save()
- self.assertEquals(len(loan.repayment_schedule), 22)
- self.assertEquals(loan.total_interest_payable, 22712)
- self.assertEquals(loan.total_payment, 302712)
+ self.assertEqual(len(loan.repayment_schedule), 22)
+ self.assertEqual(flt(loan.total_interest_payable, 0), 22712)
+ self.assertEqual(flt(loan.total_payment, 0), 302712)
def test_loan_with_security(self):
@@ -89,7 +89,7 @@
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods",
12, loan_application)
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
def test_loan_disbursement(self):
pledge = [{
@@ -102,7 +102,7 @@
create_pledge(loan_application)
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
loan.submit()
@@ -120,8 +120,8 @@
filters = {'voucher_type': 'Loan Disbursement', 'voucher_no': loan_disbursement_entry2.name}
)
- self.assertEquals(loan.status, "Disbursed")
- self.assertEquals(loan.disbursed_amount, 1000000)
+ self.assertEqual(loan.status, "Disbursed")
+ self.assertEqual(loan.disbursed_amount, 1000000)
self.assertTrue(gl_entries1)
self.assertTrue(gl_entries2)
@@ -137,7 +137,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -156,15 +156,15 @@
repayment_entry.submit()
penalty_amount = (accrued_interest_amount * 5 * 25) / 100
- self.assertEquals(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0))
+ self.assertEqual(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0))
amounts = frappe.db.get_all('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount'])
loan.load_from_db()
total_interest_paid = amounts[0]['paid_interest_amount'] + amounts[1]['paid_interest_amount']
- self.assertEquals(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable)
- self.assertEquals(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid -
+ self.assertEqual(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable)
+ self.assertEqual(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid -
penalty_amount - total_interest_paid, 0))
def test_loan_closure(self):
@@ -179,7 +179,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -204,12 +204,12 @@
amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)'])
- self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0))
- self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
+ self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
request_loan_closure(loan.name)
loan.load_from_db()
- self.assertEquals(loan.status, "Loan Closure Requested")
+ self.assertEqual(loan.status, "Loan Closure Requested")
def test_loan_repayment_for_term_loan(self):
pledges = [{
@@ -241,8 +241,8 @@
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
'paid_principal_amount'])
- self.assertEquals(amounts[0], 11250.00)
- self.assertEquals(amounts[1], 78303.00)
+ self.assertEqual(amounts[0], 11250.00)
+ self.assertEqual(amounts[1], 78303.00)
def test_security_shortfall(self):
pledges = [{
@@ -268,17 +268,17 @@
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
self.assertTrue(loan_security_shortfall)
- self.assertEquals(loan_security_shortfall.loan_amount, 1000000.00)
- self.assertEquals(loan_security_shortfall.security_value, 800000.00)
- self.assertEquals(loan_security_shortfall.shortfall_amount, 600000.00)
+ self.assertEqual(loan_security_shortfall.loan_amount, 1000000.00)
+ self.assertEqual(loan_security_shortfall.security_value, 800000.00)
+ self.assertEqual(loan_security_shortfall.shortfall_amount, 600000.00)
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
where loan_security='Test Security 2'""")
create_process_loan_security_shortfall()
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
- self.assertEquals(loan_security_shortfall.status, "Completed")
- self.assertEquals(loan_security_shortfall.shortfall_amount, 0)
+ self.assertEqual(loan_security_shortfall.status, "Completed")
+ self.assertEqual(loan_security_shortfall.shortfall_amount, 0)
def test_loan_security_unpledge(self):
pledge = [{
@@ -292,7 +292,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -312,7 +312,7 @@
request_loan_closure(loan.name)
loan.load_from_db()
- self.assertEquals(loan.status, "Loan Closure Requested")
+ self.assertEqual(loan.status, "Loan Closure Requested")
unpledge_request = unpledge_security(loan=loan.name, save=1)
unpledge_request.submit()
@@ -323,11 +323,11 @@
pledged_qty = get_pledged_security_qty(loan.name)
self.assertEqual(loan.status, 'Closed')
- self.assertEquals(sum(pledged_qty.values()), 0)
+ self.assertEqual(sum(pledged_qty.values()), 0)
amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5))
self.assertEqual(amounts['pending_principal_amount'], 0)
- self.assertEquals(amounts['payable_principal_amount'], 0.0)
+ self.assertEqual(amounts['payable_principal_amount'], 0.0)
self.assertEqual(amounts['interest_amount'], 0)
def test_partial_loan_security_unpledge(self):
@@ -346,7 +346,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -379,7 +379,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
unpledge_map = {'Test Security 1': 4000}
unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
@@ -450,7 +450,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -475,7 +475,7 @@
request_loan_closure(loan.name)
loan.load_from_db()
- self.assertEquals(loan.status, "Loan Closure Requested")
+ self.assertEqual(loan.status, "Loan Closure Requested")
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
self.assertEqual(amounts['pending_principal_amount'], 0.0)
@@ -492,7 +492,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -533,8 +533,8 @@
calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual',
{'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount')
- self.assertEquals(loan.loan_amount, 1000000)
- self.assertEquals(calculated_penalty_amount, penalty_amount)
+ self.assertEqual(loan.loan_amount, 1000000)
+ self.assertEqual(calculated_penalty_amount, penalty_amount)
def test_penalty_repayment(self):
loan, dummy = create_loan_scenario_for_penalty(self)
@@ -547,13 +547,13 @@
repayment_entry.submit()
amounts = calculate_amounts(loan.name, '2019-11-30 00:00:01')
- self.assertEquals(amounts['penalty_amount'], second_penalty)
+ self.assertEqual(amounts['penalty_amount'], second_penalty)
repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:01', second_penalty)
repayment_entry.submit()
amounts = calculate_amounts(loan.name, '2019-11-30 00:00:02')
- self.assertEquals(amounts['penalty_amount'], 0)
+ self.assertEqual(amounts['penalty_amount'], 0)
def test_loan_write_off_limit(self):
pledge = [{
@@ -567,7 +567,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -589,15 +589,15 @@
amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)'])
- self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0))
- self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
+ self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEquals(flt(amounts['pending_principal_amount'], 0), 50)
+ self.assertEqual(flt(amounts['pending_principal_amount'], 0), 50)
request_loan_closure(loan.name)
loan.load_from_db()
- self.assertEquals(loan.status, "Loan Closure Requested")
+ self.assertEqual(loan.status, "Loan Closure Requested")
def test_loan_amount_write_off(self):
pledge = [{
@@ -611,7 +611,7 @@
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -633,17 +633,17 @@
amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)'])
- self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0))
- self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
+ self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEquals(flt(amounts['pending_principal_amount'], 0), 100)
+ self.assertEqual(flt(amounts['pending_principal_amount'], 0), 100)
we = make_loan_write_off(loan.name, amount=amounts['pending_principal_amount'])
we.submit()
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
- self.assertEquals(flt(amounts['pending_principal_amount'], 0), 0)
+ self.assertEqual(flt(amounts['pending_principal_amount'], 0), 0)
def create_loan_scenario_for_penalty(doc):
pledge = [{
diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
index a875387..da56710 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
@@ -87,7 +87,7 @@
loan = create_demand_loan(self.applicant, "Demand Loan", loan_application, posting_date='2019-10-01')
loan.submit()
- self.assertEquals(loan.loan_amount, 1000000)
+ self.assertEqual(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
@@ -114,5 +114,5 @@
per_day_interest = get_per_day_interest(1500000, 13.5, '2019-10-30')
interest = per_day_interest * 15
- self.assertEquals(amounts['pending_principal_amount'], 1500000)
- self.assertEquals(amounts['interest_amount'], flt(interest + previous_interest, 2))
+ self.assertEqual(amounts['pending_principal_amount'], 1500000)
+ self.assertEqual(amounts['interest_amount'], flt(interest + previous_interest, 2))
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
index 85e008a..eb626f3 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
@@ -52,7 +52,7 @@
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
- self.assertEquals(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0))
def test_accumulated_amounts(self):
pledge = [{
@@ -76,7 +76,7 @@
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
- self.assertEquals(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0))
+ self.assertEqual(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0))
next_start_date = '2019-10-31'
next_end_date = '2019-11-29'
@@ -90,4 +90,4 @@
loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name,
'process_loan_interest_accrual': process})
- self.assertEquals(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount)
+ self.assertEqual(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount)
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 728eadf..3d99b1f 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -435,7 +435,6 @@
@frappe.whitelist()
def calculate_amounts(against_loan, posting_date, payment_type=''):
-
amounts = {
'penalty_amount': 0.0,
'interest_amount': 0.0,
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index fbfd801..a09a5e3 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -29,7 +29,10 @@
frm.set_query("item", function() {
return {
- query: "erpnext.manufacturing.doctype.bom.bom.item_query"
+ query: "erpnext.manufacturing.doctype.bom.bom.item_query",
+ filters: {
+ "is_stock_item": 1
+ }
};
});
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 979f7ca..d1f6385 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -973,6 +973,9 @@
if not has_variants:
query_filters["has_variants"] = 0
+ if filters and filters.get("is_stock_item"):
+ query_filters["is_stock_item"] = 1
+
return frappe.get_all("Item",
fields = fields, filters=query_filters,
or_filters = or_cond_filters, order_by=order_by,
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 7108338..e1cca9e 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -223,7 +223,7 @@
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1])
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
- self.assertEquals(bom_items, supplied_items)
+ self.assertEqual(bom_items, supplied_items)
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})
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
index ac9a409..80d1cdf 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
@@ -45,16 +45,16 @@
else:
doc = frappe.get_doc("BOM", bom_no)
- self.assertEquals(doc.total_cost, 200)
+ self.assertEqual(doc.total_cost, 200)
frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 200)
update_cost()
doc.load_from_db()
- self.assertEquals(doc.total_cost, 300)
+ self.assertEqual(doc.total_cost, 300)
frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 100)
update_cost()
doc.load_from_db()
- self.assertEquals(doc.total_cost, 200)
+ self.assertEqual(doc.total_cost, 200)
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 6b1fafe..cb1ee92 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -473,7 +473,7 @@
def test_cost_center_for_manufacture(self):
wo_order = make_wo_order_test_record()
ste = make_stock_entry(wo_order.name, "Material Transfer for Manufacture", wo_order.qty)
- self.assertEquals(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC")
+ self.assertEqual(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC")
def test_operation_time_with_batch_size(self):
fg_item = "Test Batch Size Item For BOM"
@@ -539,11 +539,11 @@
ste_cancel_list.append(ste1)
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
- self.assertEquals(ste3.fg_completed_qty, 2)
+ self.assertEqual(ste3.fg_completed_qty, 2)
expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4}
for row in ste3.items:
- self.assertEquals(row.qty, expected_qty.get(row.item_code))
+ self.assertEqual(row.qty, expected_qty.get(row.item_code))
ste_cancel_list.reverse()
for ste_doc in ste_cancel_list:
ste_doc.cancel()
@@ -577,7 +577,7 @@
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
for ste_row in ste3.items:
if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse:
- self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
+ self.assertEqual(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
ste3.submit()
ste_cancel_list.append(ste3)
@@ -585,7 +585,7 @@
ste2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
for ste_row in ste2.items:
if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse:
- self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
+ self.assertEqual(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
ste_cancel_list.reverse()
for ste_doc in ste_cancel_list:
ste_doc.cancel()
diff --git a/erpnext/non_profit/doctype/donation/test_donation.py b/erpnext/non_profit/doctype/donation/test_donation.py
index c6a534d..bbe9bf5 100644
--- a/erpnext/non_profit/doctype/donation/test_donation.py
+++ b/erpnext/non_profit/doctype/donation/test_donation.py
@@ -39,7 +39,7 @@
donation.on_payment_authorized()
donation.reload()
- self.assertEquals(donation.paid, 1)
+ self.assertEqual(donation.paid, 1)
self.assertTrue(frappe.db.exists('Payment Entry', {'reference_no': donation.name}))
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 9ef949c..82d223c 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -774,4 +774,7 @@
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
erpnext.patches.v13_0.update_shipment_status
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
+erpnext.patches.v12_0.add_ewaybill_validity_field
+erpnext.patches.v13_0.germany_make_custom_fields
+erpnext.patches.v13_0.germany_fill_debtor_creditor_number
erpnext.patches.v13_0.set_pos_closing_as_failed
diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
new file mode 100644
index 0000000..87d98f1
--- /dev/null
+++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
@@ -0,0 +1,16 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ custom_fields = {
+ 'Sales Invoice': [
+ dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
+ depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill')
+ ]
+ }
+ create_custom_fields(custom_fields, update=True)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
index 06331d7..a6471eb 100644
--- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
+++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
@@ -44,9 +44,11 @@
# make current item's tax map
item_tax_map = {}
for d in old_item_taxes[item_code]:
- item_tax_map[d.tax_type] = d.tax_rate
+ if d.tax_type not in item_tax_map:
+ item_tax_map[d.tax_type] = d.tax_rate
- item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code)
+ tax_types = []
+ item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code, tax_types=tax_types)
# update the item tax table
frappe.db.sql("delete from `tabItem Tax` where parent=%s and parenttype='Item'", item_code)
@@ -68,7 +70,7 @@
and item_tax_template is NULL""".format(dt), as_dict=1):
item_tax_map = json.loads(d.item_tax_rate)
item_tax_template_name = get_item_tax_template(item_tax_templates,
- item_tax_map, d.item_code, d.parenttype, d.parent)
+ item_tax_map, d.item_code, d.parenttype, d.parent, tax_types=tax_types)
frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name)
frappe.db.auto_commit_on_many_writes = False
@@ -78,7 +80,7 @@
settings.determine_address_tax_category_from = "Billing Address"
settings.save()
-def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None):
+def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None, tax_types=None):
# search for previously created item tax template by comparing tax maps
for template, item_tax_template_map in iteritems(item_tax_templates):
if item_tax_map == item_tax_template_map:
@@ -126,7 +128,9 @@
account_type = frappe.get_cached_value("Account", tax_type, "account_type")
if tax_type and account_type in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'):
- item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
+ if tax_type not in tax_types:
+ item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
+ tax_types.append(tax_type)
item_tax_templates.setdefault(item_tax_template.title, {})
item_tax_templates[item_tax_template.title][tax_type] = tax_rate
if item_tax_template.get("taxes"):
diff --git a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
index af1f6e7..77a23cf 100644
--- a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
+++ b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py
@@ -22,5 +22,7 @@
frappe.delete_doc("Page", "bank-reconciliation", force=1)
+ frappe.reload_doc('accounts', 'doctype', 'bank_transaction')
+
rename_field("Bank Transaction", "debit", "deposit")
rename_field("Bank Transaction", "credit", "withdrawal")
diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
new file mode 100644
index 0000000..11e1e9b
--- /dev/null
+++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+ """Move account number into the new custom field debtor_creditor_number.
+
+ German companies used to use a dedicated payable/receivable account for
+ every party to mimick party accounts in the external accounting software
+ "DATEV". This is no longer necessary. The reference ID for DATEV will be
+ stored in a new custom field "debtor_creditor_number".
+ """
+ company_list = frappe.get_all('Company', filters={'country': 'Germany'})
+
+ for company in company_list:
+ party_account_list = frappe.get_all('Party Account', filters={'company': company.name}, fields=['name', 'account', 'debtor_creditor_number'])
+ for party_account in party_account_list:
+ if (not party_account.account) or party_account.debtor_creditor_number:
+ # account empty or debtor_creditor_number already filled
+ continue
+
+ account_number = frappe.db.get_value('Account', party_account.account, 'account_number')
+ if not account_number:
+ continue
+
+ frappe.db.set_value('Party Account', party_account.name, 'debtor_creditor_number', account_number)
+ frappe.db.set_value('Party Account', party_account.name, 'account', '')
diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py
new file mode 100644
index 0000000..41ab945
--- /dev/null
+++ b/erpnext/patches/v13_0/germany_make_custom_fields.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from erpnext.regional.germany.setup import make_custom_fields
+
+
+def execute():
+ """Execute the make_custom_fields method for german companies.
+
+ It is usually run once at setup of a new company. Since it's new, run it
+ once for existing companies as well.
+ """
+ company_list = frappe.get_all('Company', filters = {'country': 'Germany'})
+ if not company_list:
+ return
+
+ make_custom_fields()
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index 13b6c05..ebeddf9 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -13,12 +13,19 @@
if self.ref_doctype == "Employee Advance" and self.ref_docname:
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
+ self.update_employee_referral()
+
+ def on_cancel(self):
+ self.update_employee_referral(cancel=True)
+
def validate(self):
self.validate_dates()
self.validate_salary_structure()
self.validate_recurring_additional_salary_overlap()
+ self.validate_employee_referral()
+
if self.amount < 0:
- frappe.throw(_("Amount should not be less than zero."))
+ frappe.throw(_("Amount should not be less than zero"))
def validate_salary_structure(self):
if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
@@ -70,6 +77,27 @@
if self.payroll_date and getdate(self.payroll_date) > getdate(relieving_date):
frappe.throw(_("Payroll date can not be greater than employee's relieving date."))
+ def validate_employee_referral(self):
+ if self.ref_doctype == "Employee Referral":
+ referral_details = frappe.db.get_value("Employee Referral", self.ref_docname,
+ ["is_applicable_for_referral_bonus", "status"], as_dict=1)
+
+ if not referral_details.is_applicable_for_referral_bonus:
+ frappe.throw(_("Employee Referral {0} is not applicable for referral bonus.").format(
+ self.ref_docname))
+
+ if self.type == "Deduction":
+ frappe.throw(_("Earning Salary Component is required for Employee Referral Bonus."))
+
+ if referral_details.status != "Accepted":
+ frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format(
+ frappe.bold("Accepted")))
+
+ def update_employee_referral(self, cancel=False):
+ if self.ref_doctype == "Employee Referral":
+ status = "Unpaid" if cancel else "Paid"
+ frappe.db.set_value("Employee Referral", self.ref_docname, "referral_payment_status", status)
+
def get_amount(self, sal_start_date, sal_end_date):
start_date = getdate(sal_start_date)
end_date = getdate(sal_end_date)
@@ -110,8 +138,7 @@
for d in additional_salary_list:
if d.overwrite:
if d.component in components_to_overwrite:
- frappe.throw(_("Multiple Additional Salaries with overwrite "
- "property exist for Salary Component {0} between {1} and {2}.").format(
+ frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component {0} between {1} and {2}.").format(
frappe.bold(d.component), start_date, end_date), title=_("Error"))
components_to_overwrite.append(d.component)
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js
index e00bd87..d5c20dc 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -16,11 +16,11 @@
onload: function(frm) {
let help_button = $(`<a class = 'control-label'>
- Condition and Formula Help
+ ${__("Condition and Formula Help")}
</a>`).click(()=>{
let d = new frappe.ui.Dialog({
- title: 'Condition and Formula Help',
+ title: __('Condition and Formula Help'),
fields: [
{
fieldname: 'msg_wrapper',
diff --git a/erpnext/payroll/notification/retention_bonus/retention_bonus.json b/erpnext/payroll/notification/retention_bonus/retention_bonus.json
index 50db033..37381fa 100644
--- a/erpnext/payroll/notification/retention_bonus/retention_bonus.json
+++ b/erpnext/payroll/notification/retention_bonus/retention_bonus.json
@@ -1,5 +1,6 @@
{
"attach_print": 0,
+ "channel": "Email",
"condition": "doc.docstatus==1",
"creation": "2018-05-15 18:52:36.362838",
"date_changed": "bonus_payment_date",
diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py
index bf5c402..b717491 100644
--- a/erpnext/portal/doctype/homepage/test_homepage.py
+++ b/erpnext/portal/doctype/homepage/test_homepage.py
@@ -13,7 +13,7 @@
set_request(method='GET', path='home')
response = render()
- self.assertEquals(response.status_code, 200)
+ self.assertEqual(response.status_code, 200)
html = frappe.safe_decode(response.get_data())
self.assertTrue('<section class="hero-section' in html)
diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
index 5b3196d..f0aa554 100644
--- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py
+++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
@@ -28,7 +28,7 @@
set_request(method='GET', path='home')
response = render()
- self.assertEquals(response.status_code, 200)
+ self.assertEqual(response.status_code, 200)
html = frappe.safe_decode(response.get_data())
@@ -61,7 +61,7 @@
set_request(method='GET', path='home')
response = render()
- self.assertEquals(response.status_code, 200)
+ self.assertEqual(response.status_code, 200)
html = frappe.safe_decode(response.get_data())
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index 3cdfcb2..2570df7 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -458,7 +458,7 @@
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 4,
- "modified": "2020-09-02 11:54:01.223620",
+ "modified": "2021-04-28 16:36:11.654632",
"modified_by": "Administrator",
"module": "Projects",
"name": "Project",
@@ -495,11 +495,11 @@
}
],
"quick_entry": 1,
- "search_fields": "customer, status, priority, is_active",
+ "search_fields": "project_name,customer, status, priority, is_active",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "customer",
"title_field": "project_name",
"track_seen": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index ed02f79..8d99b48 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -209,7 +209,7 @@
if parent:
condition = "AND parent = %(parent)s"
if from_time and to_time:
- condition += "AND from_time BETWEEN %(from_time)s AND %(to_time)s"
+ condition += "AND CAST(from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s"
return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt
from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1
diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py
index 5ad2d85..9139d84 100644
--- a/erpnext/projects/report/project_profitability/project_profitability.py
+++ b/erpnext/projects/report/project_profitability/project_profitability.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
+from frappe.utils import flt
def execute(filters=None):
columns, data = [], []
@@ -52,8 +53,8 @@
def calculate_cost_and_profit(data):
for row in data:
- row.fractional_cost = row.base_gross_pay * row.utilization
- row.profit = row.base_grand_total - row.base_gross_pay * row.utilization
+ row.fractional_cost = flt(row.base_gross_pay) * flt(row.utilization)
+ row.profit = flt(row.base_grand_total) - flt(row.base_gross_pay) * flt(row.utilization)
return data
def get_conditions(filters):
diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py
index ea7f1ab..2c7bb49 100644
--- a/erpnext/projects/report/project_summary/project_summary.py
+++ b/erpnext/projects/report/project_summary/project_summary.py
@@ -131,25 +131,25 @@
{
"value": avg_completion,
"indicator": "Green" if avg_completion > 50 else "Red",
- "label": "Average Completion",
+ "label": _("Average Completion"),
"datatype": "Percent",
},
{
"value": total,
"indicator": "Blue",
- "label": "Total Tasks",
+ "label": _("Total Tasks"),
"datatype": "Int",
},
{
"value": completed,
"indicator": "Green",
- "label": "Completed Tasks",
+ "label": _("Completed Tasks"),
"datatype": "Int",
},
{
"value": total_overdue,
"indicator": "Green" if total_overdue == 0 else "Red",
- "label": "Overdue Tasks",
+ "label": _("Overdue Tasks"),
"datatype": "Int",
}
]
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 121e9d0..e153e6c 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -562,7 +562,7 @@
weight_uom: item.weight_uom,
manufacturer: item.manufacturer,
stock_uom: item.stock_uom,
- pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
+ pos_profile: cint(me.frm.doc.is_pos) ? me.frm.doc.pos_profile : '',
cost_center: item.cost_center,
tax_category: me.frm.doc.tax_category,
item_tax_template: item.item_tax_template,
@@ -1329,7 +1329,7 @@
this.toggle_item_grid_columns(company_currency);
- if(this.frm.fields_dict["operations"]) {
+ if (this.frm.doc.operations && this.frm.doc.operations.length > 0) {
this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations");
this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations");
@@ -1340,7 +1340,7 @@
});
}
- if(this.frm.fields_dict["scrap_items"]) {
+ if (this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) {
this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items");
this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items");
@@ -1351,13 +1351,13 @@
});
}
- if(this.frm.fields_dict["taxes"]) {
+ if (this.frm.doc.taxes && this.frm.doc.taxes.length > 0) {
this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes");
this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes");
}
- if(this.frm.fields_dict["advances"]) {
+ if (this.frm.doc.advances && this.frm.doc.advances.length > 0) {
this.frm.set_currency_labels(["advance_amount", "allocated_amount"],
this.frm.doc.party_account_currency, "advances");
}
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 19c9073..472746a 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -48,31 +48,24 @@
return cint(frappe.boot.sysdefaults.allow_stale);
},
- setup_serial_no: function() {
- var grid_row = cur_frm.open_grid_row();
- if(!grid_row || !grid_row.grid_form.fields_dict.serial_no ||
- grid_row.grid_form.fields_dict.serial_no.get_status()!=="Write") return;
+ setup_serial_or_batch_no: function() {
+ let grid_row = cur_frm.open_grid_row();
+ if (!grid_row || !grid_row.grid_form.fields_dict.serial_no ||
+ grid_row.grid_form.fields_dict.serial_no.get_status() !== "Write") return;
- var $btn = $('<button class="btn btn-sm btn-default">'+__("Add Serial No")+'</button>')
- .appendTo($("<div>")
- .css({"margin-bottom": "10px", "margin-top": "10px"})
- .appendTo(grid_row.grid_form.fields_dict.serial_no.$wrapper));
+ frappe.model.get_value('Item', {'name': grid_row.doc.item_code},
+ ['has_serial_no', 'has_batch_no'], ({has_serial_no, has_batch_no}) => {
+ Object.assign(grid_row.doc, {has_serial_no, has_batch_no});
- var me = this;
- $btn.on("click", function() {
- let callback = '';
- let on_close = '';
-
- frappe.model.get_value('Item', {'name':grid_row.doc.item_code}, 'has_serial_no',
- (data) => {
- if(data) {
- grid_row.doc.has_serial_no = data.has_serial_no;
- me.show_serial_batch_selector(grid_row.frm, grid_row.doc,
- callback, on_close, true);
- }
+ if (has_serial_no) {
+ attach_selector_button(__("Add Serial No"),
+ grid_row.grid_form.fields_dict.serial_no.$wrapper, this, grid_row);
+ } else if (has_batch_no) {
+ attach_selector_button(__("Pick Batch No"),
+ grid_row.grid_form.fields_dict.batch_no.$wrapper, this, grid_row);
}
- );
- });
+ }
+ );
},
route_to_adjustment_jv: (args) => {
@@ -743,3 +736,14 @@
});
}
});
+
+function attach_selector_button(inner_text, append_loction, context, grid_row) {
+ let $btn_div = $("<div>").css({"margin-bottom": "10px", "margin-top": "10px"})
+ .appendTo(append_loction);
+ let $btn = $(`<button class="btn btn-sm btn-default">${inner_text}</button>`)
+ .appendTo($btn_div);
+
+ $btn.on("click", function() {
+ context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true);
+ });
+}
diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss
index 0bb8e68..9bdaa8d 100644
--- a/erpnext/public/scss/point-of-sale.scss
+++ b/erpnext/public/scss/point-of-sale.scss
@@ -129,11 +129,20 @@
@extend .pointer-no-select;
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-base);
+ position: relative;
&:hover {
transform: scale(1.02, 1.02);
}
+ .item-qty-pill {
+ position: absolute;
+ display: flex;
+ margin: var(--margin-sm);
+ justify-content: flex-end;
+ right: 0px;
+ }
+
.item-display {
display: flex;
align-items: center;
@@ -766,9 +775,10 @@
> .payment-modes {
display: flex;
padding-bottom: var(--padding-sm);
- margin-bottom: var(--margin-xs);
+ margin-bottom: var(--margin-sm);
overflow-x: scroll;
overflow-y: hidden;
+ flex-shrink: 0;
> .payment-mode-wrapper {
min-width: 40%;
@@ -825,9 +835,24 @@
> .fields-numpad-container {
display: flex;
flex: 1;
+ height: 100%;
+ position: relative;
+ justify-content: flex-end;
> .fields-section {
flex: 1;
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ width: 50%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ padding-bottom: var(--margin-md);
+
+ .invoice-fields {
+ overflow-y: scroll;
+ }
}
> .number-pad {
@@ -835,6 +860,7 @@
display: flex;
justify-content: flex-end;
align-items: flex-end;
+ max-width: 50%;
.numpad-container {
display: grid;
@@ -861,6 +887,7 @@
margin-bottom: var(--margin-sm);
justify-content: center;
flex-direction: column;
+ flex-shrink: 0;
> .totals {
display: flex;
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py
index 346ebbf..c478b0f 100644
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py
+++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py
@@ -49,11 +49,11 @@
certificate.insert()
# check company details
- self.assertEquals(certificate.company_pan_number, 'BBBTI3374C')
- self.assertEquals(certificate.company_80g_number, 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087')
+ self.assertEqual(certificate.company_pan_number, 'BBBTI3374C')
+ self.assertEqual(certificate.company_80g_number, 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087')
# check donation details
- self.assertEquals(certificate.amount, donation.amount)
+ self.assertEqual(certificate.amount, donation.amount)
duplicate_certificate = create_80g_certificate(args)
# duplicate validation
@@ -83,9 +83,9 @@
certificate.get_payments()
certificate.insert()
- self.assertEquals(len(certificate.payments), 1)
- self.assertEquals(certificate.payments[0].amount, membership.amount)
- self.assertEquals(certificate.payments[0].invoice_id, invoice.name)
+ self.assertEqual(len(certificate.payments), 1)
+ self.assertEqual(certificate.payments[0].amount, membership.amount)
+ self.assertEqual(certificate.payments[0].invoice_id, invoice.name)
def create_80g_certificate(args):
diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py
index ac1f543..c1fa6e4 100644
--- a/erpnext/regional/germany/setup.py
+++ b/erpnext/regional/germany/setup.py
@@ -1,11 +1,24 @@
import os
import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def setup(company=None, patch=True):
+ make_custom_fields()
add_custom_roles_for_reports()
+def make_custom_fields():
+ custom_fields = {
+ 'Party Account': [
+ dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number',
+ fieldtype='Data', insert_after='account', translatable=0)
+ ]
+ }
+
+ create_custom_fields(custom_fields)
+
+
def add_custom_roles_for_reports():
"""Add Access Control to UAE VAT 201."""
if not frappe.db.get_value('Custom Role', dict(report='DATEV')):
@@ -16,4 +29,4 @@
dict(role='Accounts User'),
dict(role='Accounts Manager')
]
- )).insert()
\ No newline at end of file
+ )).insert()
diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py
index f138a80..826d51f 100644
--- a/erpnext/regional/germany/utils/datev/datev_csv.py
+++ b/erpnext/regional/germany/utils/datev/datev_csv.py
@@ -56,10 +56,10 @@
)
if not six.PY2:
- data = data.encode('latin_1')
+ data = data.encode('latin_1', errors='replace')
header = get_header(filters, csv_class)
- header = ';'.join(header).encode('latin_1')
+ header = ';'.join(header).encode('latin_1', errors='replace')
# 1st Row: Header with meta data
# 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here.
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 699441b..b4e7a88 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -71,13 +71,14 @@
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 += _('document id {} exceed 16 letters. ').format(bold(_('should not')))
+ 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 += '<br><br>'
- msg += _('You must {} your {} in order to have document id of {} length 16. ').format(
+ 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. ')
+ msg += _('Please account for ammended documents too.')
frappe.throw(msg, title=title)
def read_json(name):
@@ -847,6 +848,7 @@
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 = {
@@ -944,6 +946,7 @@
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
@@ -960,6 +963,7 @@
'label': _('IRN Generated')
}
self.update_invoice()
+
def attach_qrcode_image(self):
qrcode = self.invoice.signed_qr_code
doctype = self.invoice.doctype
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 9ded8da..b12e152 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -422,6 +422,9 @@
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+ dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
+ depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'),
+
dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 6338056..052d7bd 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -879,3 +879,24 @@
if total_charges != additional_taxes:
diff = additional_taxes - total_charges
doc.get('items')[item_count - 1].taxable_value += diff
+
+def get_depreciation_amount(asset, depreciable_value, row):
+ depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
+
+ if row.depreciation_method in ("Straight Line", "Manual"):
+ depreciation_amount = (flt(row.value_after_depreciation) -
+ flt(row.expected_value_after_useful_life)) / depreciation_left
+ else:
+ rate_of_depreciation = row.rate_of_depreciation
+ # if its the first depreciation
+ if depreciable_value == asset.gross_purchase_amount:
+ # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2
+ diff = date_diff(asset.available_for_use_date, row.depreciation_start_date)
+ if diff <= 180:
+ rate_of_depreciation = rate_of_depreciation / 2
+ frappe.msgprint(
+ _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.'))
+
+ depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100))
+
+ return depreciation_amount
\ No newline at end of file
diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
index cbc9478..a5ca7ee 100644
--- a/erpnext/regional/report/datev/datev.py
+++ b/erpnext/regional/report/datev/datev.py
@@ -3,9 +3,9 @@
Provide a report and downloadable CSV according to the German DATEV format.
- Query report showing only the columns that contain data, formatted nicely for
- dispay to the user.
+ dispay to the user.
- CSV download functionality `download_datev_csv` that provides a CSV file with
- all required columns. Used to import the data into the DATEV Software.
+ all required columns. Used to import the data into the DATEV Software.
"""
from __future__ import unicode_literals
@@ -88,6 +88,32 @@
"fieldtype": "Dynamic Link",
"options": "Beleginfo - Art 2",
"width": 150
+ },
+ {
+ "label": "Beleginfo - Art 3",
+ "fieldname": "Beleginfo - Art 3",
+ "fieldtype": "Link",
+ "options": "DocType",
+ "width": 100
+ },
+ {
+ "label": "Beleginfo - Inhalt 3",
+ "fieldname": "Beleginfo - Inhalt 3",
+ "fieldtype": "Dynamic Link",
+ "options": "Beleginfo - Art 3",
+ "width": 150
+ },
+ {
+ "label": "Beleginfo - Art 4",
+ "fieldname": "Beleginfo - Art 4",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": "Beleginfo - Inhalt 4",
+ "fieldname": "Beleginfo - Inhalt 4",
+ "fieldtype": "Data",
+ "width": 150
}
]
@@ -120,10 +146,8 @@
validate_fiscal_year(from_date, to_date, company)
if not frappe.db.exists('DATEV Settings', filters.get('company')):
- frappe.log_error(_('Please create {} for Company {}.').format(
- '<a href="desk#List/DATEV%20Settings/List">{}</a>'.format(_('DATEV Settings')),
- frappe.bold(filters.get('company'))
- ))
+ msg = 'Please create DATEV Settings for Company {}'.format(filters.get('company'))
+ frappe.log_error(msg, title='DATEV Settings missing')
return False
return True
@@ -169,7 +193,11 @@
gl.voucher_type as 'Beleginfo - Art 1',
gl.voucher_no as 'Beleginfo - Inhalt 1',
gl.against_voucher_type as 'Beleginfo - Art 2',
- gl.against_voucher as 'Beleginfo - Inhalt 2'
+ gl.against_voucher as 'Beleginfo - Inhalt 2',
+ gl.party_type as 'Beleginfo - Art 3',
+ gl.party as 'Beleginfo - Inhalt 3',
+ case gl.party_type when 'Customer' then 'Debitorennummer' when 'Supplier' then 'Kreditorennummer' else NULL end as 'Beleginfo - Art 4',
+ par.debtor_creditor_number as 'Beleginfo - Inhalt 4'
FROM `tabGL Entry` gl
@@ -177,6 +205,19 @@
left join `tabAccount` acc
on gl.account = acc.name
+ left join `tabCustomer` cus
+ on gl.party_type = 'Customer'
+ and gl.party = cus.name
+
+ left join `tabSupplier` sup
+ on gl.party_type = 'Supplier'
+ and gl.party = sup.name
+
+ left join `tabParty Account` par
+ on par.parent = gl.party
+ and par.parenttype = gl.party_type
+ and par.company = %(company)s
+
WHERE gl.company = %(company)s
AND DATE(gl.posting_date) >= %(from_date)s
AND DATE(gl.posting_date) <= %(to_date)s
@@ -196,40 +237,56 @@
return frappe.db.sql("""
SELECT
- acc.account_number as 'Konto',
- CASE cus.customer_type WHEN 'Company' THEN cus.customer_name ELSE null END as 'Name (Adressatentyp Unternehmen)',
- CASE cus.customer_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
- CASE cus.customer_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)',
- CASE cus.customer_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp',
+ par.debtor_creditor_number as 'Konto',
+ CASE cus.customer_type
+ WHEN 'Company' THEN cus.customer_name
+ ELSE null
+ END as 'Name (Adressatentyp Unternehmen)',
+ CASE cus.customer_type
+ WHEN 'Individual' THEN TRIM(SUBSTR(cus.customer_name, LOCATE(' ', cus.customer_name)))
+ ELSE null
+ END as 'Name (Adressatentyp natürl. Person)',
+ CASE cus.customer_type
+ WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(cus.customer_name, ' ', 1), ' ', -1)
+ ELSE null
+ END as 'Vorname (Adressatentyp natürl. Person)',
+ CASE cus.customer_type
+ WHEN 'Individual' THEN '1'
+ WHEN 'Company' THEN '2'
+ ELSE '0'
+ END as 'Adressatentyp',
adr.address_line1 as 'Straße',
adr.pincode as 'Postleitzahl',
adr.city as 'Ort',
UPPER(country.code) as 'Land',
adr.address_line2 as 'Adresszusatz',
- con.email_id as 'E-Mail',
- coalesce(con.mobile_no, con.phone) as 'Telefon',
+ adr.email_id as 'E-Mail',
+ adr.phone as 'Telefon',
+ adr.fax as 'Fax',
cus.website as 'Internet',
cus.tax_id as 'Steuernummer'
- FROM `tabParty Account` par
+ FROM `tabCustomer` cus
- left join `tabAccount` acc
- on acc.name = par.account
+ left join `tabParty Account` par
+ on par.parent = cus.name
+ and par.parenttype = 'Customer'
+ and par.company = %(company)s
- left join `tabCustomer` cus
- on cus.name = par.parent
+ left join `tabDynamic Link` dyn_adr
+ on dyn_adr.link_name = cus.name
+ and dyn_adr.link_doctype = 'Customer'
+ and dyn_adr.parenttype = 'Address'
left join `tabAddress` adr
- on adr.name = cus.customer_primary_address
+ on adr.name = dyn_adr.parent
+ and adr.is_primary_address = '1'
left join `tabCountry` country
on country.name = adr.country
- left join `tabContact` con
- on con.name = cus.customer_primary_contact
-
- WHERE par.company = %(company)s
- AND par.parenttype = 'Customer'""", filters, as_dict=1)
+ WHERE adr.is_primary_address = '1'
+ """, filters, as_dict=1)
def get_suppliers(filters):
@@ -242,35 +299,48 @@
return frappe.db.sql("""
SELECT
- acc.account_number as 'Konto',
- CASE sup.supplier_type WHEN 'Company' THEN sup.supplier_name ELSE null END as 'Name (Adressatentyp Unternehmen)',
- CASE sup.supplier_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
- CASE sup.supplier_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)',
- CASE sup.supplier_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp',
+ par.debtor_creditor_number as 'Konto',
+ CASE sup.supplier_type
+ WHEN 'Company' THEN sup.supplier_name
+ ELSE null
+ END as 'Name (Adressatentyp Unternehmen)',
+ CASE sup.supplier_type
+ WHEN 'Individual' THEN TRIM(SUBSTR(sup.supplier_name, LOCATE(' ', sup.supplier_name)))
+ ELSE null
+ END as 'Name (Adressatentyp natürl. Person)',
+ CASE sup.supplier_type
+ WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(sup.supplier_name, ' ', 1), ' ', -1)
+ ELSE null
+ END as 'Vorname (Adressatentyp natürl. Person)',
+ CASE sup.supplier_type
+ WHEN 'Individual' THEN '1'
+ WHEN 'Company' THEN '2'
+ ELSE '0'
+ END as 'Adressatentyp',
adr.address_line1 as 'Straße',
adr.pincode as 'Postleitzahl',
adr.city as 'Ort',
UPPER(country.code) as 'Land',
adr.address_line2 as 'Adresszusatz',
- con.email_id as 'E-Mail',
- coalesce(con.mobile_no, con.phone) as 'Telefon',
+ adr.email_id as 'E-Mail',
+ adr.phone as 'Telefon',
+ adr.fax as 'Fax',
sup.website as 'Internet',
sup.tax_id as 'Steuernummer',
case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis'
- FROM `tabParty Account` par
+ FROM `tabSupplier` sup
- left join `tabAccount` acc
- on acc.name = par.account
-
- left join `tabSupplier` sup
- on sup.name = par.parent
+ left join `tabParty Account` par
+ on par.parent = sup.name
+ and par.parenttype = 'Supplier'
+ and par.company = %(company)s
left join `tabDynamic Link` dyn_adr
on dyn_adr.link_name = sup.name
and dyn_adr.link_doctype = 'Supplier'
and dyn_adr.parenttype = 'Address'
-
+
left join `tabAddress` adr
on adr.name = dyn_adr.parent
and adr.is_primary_address = '1'
@@ -278,17 +348,8 @@
left join `tabCountry` country
on country.name = adr.country
- left join `tabDynamic Link` dyn_con
- on dyn_con.link_name = sup.name
- and dyn_con.link_doctype = 'Supplier'
- and dyn_con.parenttype = 'Contact'
-
- left join `tabContact` con
- on con.name = dyn_con.parent
- and con.is_primary_contact = '1'
-
- WHERE par.company = %(company)s
- AND par.parenttype = 'Supplier'""", filters, as_dict=1)
+ WHERE adr.is_primary_address = '1'
+ """, filters, as_dict=1)
def get_account_names(filters):
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 49ca942..51d86ff 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -490,7 +490,7 @@
outstanding_based_on_gle = flt(outstanding_based_on_gle[0][0]) if outstanding_based_on_gle else 0
# Outstanding based on Sales Order
- outstanding_based_on_so = 0.0
+ outstanding_based_on_so = 0
# if credit limit check is bypassed at sales order level,
# we should not consider outstanding Sales Orders, when customer credit balance report is run
@@ -501,9 +501,11 @@
where customer=%s and docstatus = 1 and company=%s
and per_billed < 100 and status != 'Closed'""", (customer, company))
- outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0.0
+ outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0
# Outstanding based on Delivery Note, which are not created against Sales Order
+ outstanding_based_on_dn = 0
+
unmarked_delivery_note_items = frappe.db.sql("""select
dn_item.name, dn_item.amount, dn.base_net_total, dn.base_grand_total
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
@@ -515,15 +517,29 @@
and ifnull(dn_item.against_sales_invoice, '') = ''
""", (customer, company), as_dict=True)
- outstanding_based_on_dn = 0.0
+ if not unmarked_delivery_note_items:
+ return outstanding_based_on_gle + outstanding_based_on_so
+
+ si_amounts = frappe.db.sql("""
+ SELECT
+ dn_detail, sum(amount) from `tabSales Invoice Item`
+ WHERE
+ docstatus = 1
+ and dn_detail in ({})
+ GROUP BY dn_detail""".format(", ".join(
+ frappe.db.escape(dn_item.name)
+ for dn_item in unmarked_delivery_note_items
+ ))
+ )
+
+ si_amounts = {si_item[0]: si_item[1] for si_item in si_amounts}
for dn_item in unmarked_delivery_note_items:
- si_amount = frappe.db.sql("""select sum(amount)
- from `tabSales Invoice Item`
- where dn_detail = %s and docstatus = 1""", dn_item.name)[0][0]
+ dn_amount = flt(dn_item.amount)
+ si_amount = flt(si_amounts.get(dn_item.name))
- if flt(dn_item.amount) > flt(si_amount) and dn_item.base_net_total:
- outstanding_based_on_dn += ((flt(dn_item.amount) - flt(si_amount)) \
+ if dn_amount > si_amount and dn_item.base_net_total:
+ outstanding_based_on_dn += ((dn_amount - si_amount)
/ dn_item.base_net_total) * dn_item.base_grand_total
return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index f0143f3..527a999 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -48,7 +48,7 @@
sales_order.transaction_date = nowdate()
sales_order.insert()
- self.assertEquals(sales_order.currency, "USD")
+ self.assertEqual(sales_order.currency, "USD")
self.assertNotEqual(sales_order.currency, quotation.currency)
def test_make_sales_order(self):
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 3137621..9873710 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -85,7 +85,7 @@
si1.update_billed_amount_in_sales_order = 1
si1.submit()
so.load_from_db()
- self.assertEquals(so.per_billed, 0)
+ self.assertEqual(so.per_billed, 0)
def test_make_sales_invoice_with_terms(self):
so = make_sales_order(do_not_submit=True)
@@ -996,7 +996,7 @@
# Check if Work Orders were raised
for item in so_item_name:
wo_qty = frappe.db.sql("select sum(qty) from `tabWork Order` where sales_order=%s and sales_order_item=%s", (so.name, item))
- self.assertEquals(wo_qty[0][0], so_item_name.get(item))
+ self.assertEqual(wo_qty[0][0], so_item_name.get(item))
def test_serial_no_based_delivery(self):
frappe.set_value("Stock Settings", None, "automatically_set_serial_nos_based_on_fifo", 1)
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 062cba1..750a1a6 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -23,7 +23,7 @@
if search_value:
data = search_serial_or_batch_or_barcode_number(search_value)
-
+
item_code = data.get("item_code") if data.get("item_code") else search_value
serial_no = data.get("serial_no") if data.get("serial_no") else ""
batch_no = data.get("batch_no") if data.get("batch_no") else ""
@@ -31,7 +31,7 @@
if data:
item_info = frappe.db.get_value(
- "Item", data.get("item_code"),
+ "Item", data.get("item_code"),
["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"]
, as_dict=1)
item_info.setdefault('serial_no', serial_no)
@@ -139,8 +139,24 @@
if serial_no or batch_no or barcode:
return "item.name = {0}".format(frappe.db.escape(item_code))
- return """(item.name like {item_code}
- or item.item_name like {item_code})""".format(item_code = frappe.db.escape('%' + item_code + '%'))
+ return make_condition(item_code)
+
+def make_condition(item_code):
+ condition = "("
+ condition += """item.name like {item_code}
+ or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%'))
+ condition += add_search_fields_condition(item_code)
+ condition += ")"
+
+ return condition
+
+def add_search_fields_condition(item_code):
+ condition = ''
+ search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname'])
+ if search_fields:
+ for field in search_fields:
+ condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%'))
+ return condition
def get_item_group_condition(pos_profile):
cond = "and 1=1"
@@ -257,4 +273,4 @@
elif fieldname == 'mobile_no':
contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
frappe.db.set_value('Customer', customer, 'mobile_no', value)
- contact_doc.save()
\ No newline at end of file
+ contact_doc.save()
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 8adf5bf..8e0a1e1 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -58,7 +58,7 @@
}
const pos_profile_query = {
query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
- filters: { company: frappe.defaults.get_default('company') }
+ filters: { company: dialog.fields_dict.company.get_value() }
}
const dialog = new frappe.ui.Dialog({
title: __('Create POS Opening Entry'),
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index 709fe57..b8a82a9 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -81,13 +81,26 @@
const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
+ let qty_to_display = actual_qty;
+
+ if (Math.round(qty_to_display) > 999) {
+ qty_to_display = Math.round(qty_to_display)/1000;
+ qty_to_display = qty_to_display.toFixed(1) + 'K';
+ }
+
function get_item_image_html() {
if (!me.hide_images && item_image) {
- return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
+ return `<div class="item-qty-pill">
+ <span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
+ </div>
+ <div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
</div>`;
} else {
- return `<div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
+ return `<div class="item-qty-pill">
+ <span class="indicator-pill whitespace-nowrap ${indicator_color}">${qty_to_display}</span>
+ </div>
+ <div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
}
}
@@ -95,13 +108,12 @@
`<div class="item-wrapper"
data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
- title="Avaiable Qty: ${actual_qty}">
+ title="${item.item_name}">
${get_item_image_html()}
<div class="item-detail">
<div class="item-name">
- <span class="indicator ${indicator_color}"></span>
${frappe.ellipsis(item.item_name, 18)}
</div>
<div class="item-rate">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
@@ -316,4 +328,4 @@
toggle_component(show) {
show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none');
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index c2b5e4f..9957aad 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -169,9 +169,9 @@
return;
}
frappe.call({
- method: "erpnext.setup.doctype.company.delete_company_transactions.delete_company_transactions",
+ method: "erpnext.setup.doctype.company.company.create_transaction_deletion_request",
args: {
- company_name: data.company_name
+ company: data.company_name
},
freeze: true,
callback: function(r, rt) {
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 64e027d..077538d 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -613,4 +613,13 @@
if out:
return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0]
else:
- return None
\ No newline at end of file
+ return None
+
+@frappe.whitelist()
+def create_transaction_deletion_request(company):
+ tdr = frappe.get_doc({
+ 'doctype': 'Transaction Deletion Record',
+ 'company': company
+ })
+ tdr.insert()
+ tdr.submit()
diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py
deleted file mode 100644
index 8367a25..0000000
--- a/erpnext/setup/doctype/company/delete_company_transactions.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-
-from frappe.utils import cint
-from frappe import _
-from frappe.desk.notifications import clear_notifications
-
-import functools
-
-@frappe.whitelist()
-def delete_company_transactions(company_name):
- frappe.only_for("System Manager")
- doc = frappe.get_doc("Company", company_name)
-
- if frappe.session.user != doc.owner and frappe.session.user != 'Administrator':
- frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
- frappe.PermissionError)
-
- delete_bins(company_name)
- delete_lead_addresses(company_name)
-
- for doctype in frappe.db.sql_list("""select parent from
- tabDocField where fieldtype='Link' and options='Company'"""):
- if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
- "Party Account", "Employee", "Sales Taxes and Charges Template",
- "Purchase Taxes and Charges Template", "POS Profile", "BOM",
- "Company", "Bank Account", "Item Tax Template", "Mode Of Payment", "Mode of Payment Account",
- "Item Default", "Customer", "Supplier", "GST Account"):
- delete_for_doctype(doctype, company_name)
-
- # reset company values
- doc.total_monthly_sales = 0
- doc.sales_monthly_history = None
- doc.save()
- # Clear notification counts
- clear_notifications()
-
-def delete_for_doctype(doctype, company_name):
- meta = frappe.get_meta(doctype)
- company_fieldname = meta.get("fields", {"fieldtype": "Link",
- "options": "Company"})[0].fieldname
-
- if not meta.issingle:
- if not meta.istable:
- # delete communication
- delete_communications(doctype, company_name, company_fieldname)
-
- # delete children
- for df in meta.get_table_fields():
- frappe.db.sql("""delete from `tab{0}` where parent in
- (select name from `tab{1}` where `{2}`=%s)""".format(df.options,
- doctype, company_fieldname), company_name)
-
- #delete version log
- frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in
- (select name from `tab{0}` where `{1}`=%s)""".format(doctype,
- company_fieldname), (doctype, company_name))
-
- # delete parent
- frappe.db.sql("""delete from `tab{0}`
- where {1}= %s """.format(doctype, company_fieldname), company_name)
-
- # reset series
- naming_series = meta.get_field("naming_series")
- if naming_series and naming_series.options:
- prefixes = sorted(naming_series.options.split("\n"),
- key=functools.cmp_to_key(lambda a, b: len(b) - len(a)))
-
- for prefix in prefixes:
- if prefix:
- last = frappe.db.sql("""select max(name) from `tab{0}`
- where name like %s""".format(doctype), prefix + "%")
- if last and last[0][0]:
- last = cint(last[0][0].replace(prefix, ""))
- else:
- last = 0
-
- frappe.db.sql("""update tabSeries set current = %s
- where name=%s""", (last, prefix))
-
-def delete_bins(company_name):
- frappe.db.sql("""delete from tabBin where warehouse in
- (select name from tabWarehouse where company=%s)""", company_name)
-
-def delete_lead_addresses(company_name):
- """Delete addresses to which leads are linked"""
- leads = frappe.get_all("Lead", filters={"company": company_name})
- leads = [ "'%s'"%row.get("name") for row in leads ]
- addresses = []
- if leads:
- addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
- in ({leads})""".format(leads=",".join(leads)))
-
- if addresses:
- addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
-
- frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
- name not in (select distinct dl1.parent from `tabDynamic Link` dl1
- inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
- and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
-
- frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
- and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
-
- frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
-
-def delete_communications(doctype, company_name, company_fieldname):
- reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name})
- reference_doc_names = [r.name for r in reference_docs]
-
- communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]})
- communication_names = [c.name for c in communications]
-
- frappe.delete_doc("Communication", communication_names, ignore_permissions=True)
diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py
index 29f6c37..e1c803a 100644
--- a/erpnext/setup/doctype/company/test_company.py
+++ b/erpnext/setup/doctype/company/test_company.py
@@ -86,15 +86,6 @@
self.delete_mode_of_payment(template)
frappe.delete_doc("Company", template)
- def test_delete_communication(self):
- from erpnext.setup.doctype.company.delete_company_transactions import delete_communications
- company = create_child_company()
- lead = create_test_lead_in_company(company)
- communication = create_company_communication("Lead", lead)
- delete_communications("Lead", "Test Company", "company")
- self.assertFalse(frappe.db.exists("Communcation", communication))
- self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication}))
-
def delete_mode_of_payment(self, company):
frappe.db.sql(""" delete from `tabMode of Payment Account`
where company =%s """, (company))
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index ac55fdf..8c97322 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -50,8 +50,12 @@
recipients = list(filter(lambda r: r in valid_users,
self.recipient_list.split("\n")))
+ original_user = frappe.session.user
+
if recipients:
for user_id in recipients:
+ frappe.set_user(user_id)
+ frappe.set_user_lang(user_id)
msg_for_this_recipient = self.get_msg_html()
if msg_for_this_recipient:
frappe.sendmail(
@@ -62,6 +66,9 @@
reference_name = self.name,
unsubscribe_message = _("Unsubscribe from this Email Digest"))
+ frappe.set_user(original_user)
+ frappe.set_user_lang(original_user)
+
def get_msg_html(self):
"""Build email digest content"""
frappe.flags.ignore_account_permission = True
diff --git a/erpnext/setup/doctype/transaction_deletion_record/__init__.py b/erpnext/setup/doctype/transaction_deletion_record/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/__init__.py
diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
new file mode 100644
index 0000000..bbe6836
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestTransactionDeletionRecord(unittest.TestCase):
+ def setUp(self):
+ create_company('Dunder Mifflin Paper Co')
+
+ def tearDown(self):
+ frappe.db.rollback()
+
+ def test_doctypes_contain_company_field(self):
+ tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co')
+ for doctype in tdr.doctypes:
+ contains_company = False
+ doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()['fields']
+ for doctype_field in doctype_fields:
+ if doctype_field['fieldtype'] == 'Link' and doctype_field['options'] == 'Company':
+ contains_company = True
+ break
+ self.assertTrue(contains_company)
+
+ def test_no_of_docs_is_correct(self):
+ for i in range(5):
+ create_task('Dunder Mifflin Paper Co')
+ tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co')
+ for doctype in tdr.doctypes:
+ if doctype.doctype_name == 'Task':
+ self.assertEqual(doctype.no_of_docs, 5)
+
+ def test_deletion_is_successful(self):
+ create_task('Dunder Mifflin Paper Co')
+ create_transaction_deletion_request('Dunder Mifflin Paper Co')
+ tasks_containing_company = frappe.get_all('Task',
+ filters = {
+ 'company' : 'Dunder Mifflin Paper Co'
+ })
+ self.assertEqual(tasks_containing_company, [])
+
+def create_company(company_name):
+ company = frappe.get_doc({
+ 'doctype': 'Company',
+ 'company_name': company_name,
+ 'default_currency': 'INR'
+ })
+ company.insert(ignore_if_duplicate = True)
+
+def create_transaction_deletion_request(company):
+ tdr = frappe.get_doc({
+ 'doctype': 'Transaction Deletion Record',
+ 'company': company
+ })
+ tdr.insert()
+ tdr.submit()
+ return tdr
+
+
+def create_task(company):
+ task = frappe.get_doc({
+ 'doctype': 'Task',
+ 'company': company,
+ 'subject': 'Delete'
+ })
+ task.insert()
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js
new file mode 100644
index 0000000..20caa15
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Transaction Deletion Record', {
+ onload: function(frm) {
+ if (frm.doc.docstatus == 0) {
+ let doctypes_to_be_ignored_array;
+ frappe.call({
+ method: 'erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_doctypes_to_be_ignored',
+ callback: function(r) {
+ doctypes_to_be_ignored_array = r.message;
+ populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm);
+ frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
+ frm.refresh_field('doctypes_to_be_ignored');
+ }
+ });
+ }
+
+ frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true;
+ frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
+ frm.refresh_field('doctypes_to_be_ignored');
+ },
+
+ refresh: function(frm) {
+ frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
+ frm.refresh_field('doctypes_to_be_ignored');
+ }
+
+});
+
+function populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm) {
+ if (!(frm.doc.doctypes_to_be_ignored)) {
+ var i;
+ for (i = 0; i < doctypes_to_be_ignored_array.length; i++) {
+ frm.add_child('doctypes_to_be_ignored', {
+ doctype_name: doctypes_to_be_ignored_array[i]
+ });
+ }
+ }
+}
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
new file mode 100644
index 0000000..9313f95
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
@@ -0,0 +1,79 @@
+{
+ "actions": [],
+ "autoname": "TDL.####",
+ "creation": "2021-04-06 20:17:18.404716",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "doctypes",
+ "doctypes_to_be_ignored",
+ "amended_from",
+ "status"
+ ],
+ "fields": [
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "fieldname": "doctypes",
+ "fieldtype": "Table",
+ "label": "Summary",
+ "options": "Transaction Deletion Record Item",
+ "read_only": 1
+ },
+ {
+ "fieldname": "doctypes_to_be_ignored",
+ "fieldtype": "Table",
+ "label": "Excluded DocTypes",
+ "options": "Transaction Deletion Record Item"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Transaction Deletion Record",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Status",
+ "options": "Draft\nCompleted"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-05-08 23:13:48.049879",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Transaction Deletion Record",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
new file mode 100644
index 0000000..38f8de7
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+from frappe.utils import cint
+import frappe
+from frappe.model.document import Document
+from frappe import _
+from frappe.desk.notifications import clear_notifications
+
+class TransactionDeletionRecord(Document):
+ def validate(self):
+ frappe.only_for('System Manager')
+ company_obj = frappe.get_doc('Company', self.company)
+ if frappe.session.user != company_obj.owner and frappe.session.user != 'Administrator':
+ frappe.throw(_('Transactions can only be deleted by the creator of the Company or the Administrator.'),
+ frappe.PermissionError)
+ doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
+ for doctype in self.doctypes_to_be_ignored:
+ if doctype.doctype_name not in doctypes_to_be_ignored_list:
+ frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed"))
+
+ def before_submit(self):
+ if not self.doctypes_to_be_ignored:
+ self.populate_doctypes_to_be_ignored_table()
+
+ self.delete_bins()
+ self.delete_lead_addresses()
+
+ company_obj = frappe.get_doc('Company', self.company)
+ # reset company values
+ company_obj.total_monthly_sales = 0
+ company_obj.sales_monthly_history = None
+ company_obj.save()
+ # Clear notification counts
+ clear_notifications()
+
+ singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
+ tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
+ doctypes_to_be_ignored_list = singles
+ for doctype in self.doctypes_to_be_ignored:
+ doctypes_to_be_ignored_list.append(doctype.doctype_name)
+
+ docfields = frappe.get_all('DocField',
+ filters = {
+ 'fieldtype': 'Link',
+ 'options': 'Company',
+ 'parent': ['not in', doctypes_to_be_ignored_list]},
+ fields=['parent', 'fieldname'])
+
+ for docfield in docfields:
+ if docfield['parent'] != self.doctype:
+ no_of_docs = frappe.db.count(docfield['parent'], {
+ docfield['fieldname'] : self.company
+ })
+
+ if no_of_docs > 0:
+ self.delete_version_log(docfield['parent'], docfield['fieldname'])
+ self.delete_communications(docfield['parent'], docfield['fieldname'])
+
+ # populate DocTypes table
+ if docfield['parent'] not in tables:
+ self.append('doctypes', {
+ 'doctype_name' : docfield['parent'],
+ 'no_of_docs' : no_of_docs
+ })
+
+ # delete the docs linked with the specified company
+ frappe.db.delete(docfield['parent'], {
+ docfield['fieldname'] : self.company
+ })
+
+ naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
+ if naming_series:
+ if '#' in naming_series:
+ self.update_naming_series(naming_series, docfield['parent'])
+
+ def populate_doctypes_to_be_ignored_table(self):
+ doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
+ for doctype in doctypes_to_be_ignored_list:
+ self.append('doctypes_to_be_ignored', {
+ 'doctype_name' : doctype
+ })
+
+ def update_naming_series(self, naming_series, doctype_name):
+ if '.' in naming_series:
+ prefix, hashes = naming_series.rsplit('.', 1)
+ else:
+ prefix, hashes = naming_series.rsplit('{', 1)
+ last = frappe.db.sql("""select max(name) from `tab{0}`
+ where name like %s""".format(doctype_name), prefix + '%')
+ if last and last[0][0]:
+ last = cint(last[0][0].replace(prefix, ''))
+ else:
+ last = 0
+
+ frappe.db.sql("""update tabSeries set current = %s where name=%s""", (last, prefix))
+
+ def delete_version_log(self, doctype, company_fieldname):
+ frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in
+ (select name from `tab{0}` where `{1}`=%s)""".format(doctype,
+ company_fieldname), (doctype, self.company))
+
+ def delete_communications(self, doctype, company_fieldname):
+ reference_docs = frappe.get_all(doctype, filters={company_fieldname:self.company})
+ reference_doc_names = [r.name for r in reference_docs]
+
+ communications = frappe.get_all('Communication', filters={'reference_doctype':doctype,'reference_name':['in', reference_doc_names]})
+ communication_names = [c.name for c in communications]
+
+ frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
+
+ def delete_bins(self):
+ frappe.db.sql("""delete from tabBin where warehouse in
+ (select name from tabWarehouse where company=%s)""", self.company)
+
+ def delete_lead_addresses(self):
+ """Delete addresses to which leads are linked"""
+ leads = frappe.get_all('Lead', filters={'company': self.company})
+ leads = ["'%s'" % row.get("name") for row in leads]
+ addresses = []
+ if leads:
+ addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
+ in ({leads})""".format(leads=",".join(leads)))
+
+ if addresses:
+ addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
+
+ frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
+ name not in (select distinct dl1.parent from `tabDynamic Link` dl1
+ inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
+ and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
+
+ frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
+ and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
+
+ frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
+
+@frappe.whitelist()
+def get_doctypes_to_be_ignored():
+ doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',
+ 'Party Account', 'Employee', 'Sales Taxes and Charges Template',
+ 'Purchase Taxes and Charges Template', 'POS Profile', 'BOM',
+ 'Company', 'Bank Account', 'Item Tax Template', 'Mode of Payment',
+ 'Item Default', 'Customer', 'Supplier', 'GST Account']
+ return doctypes_to_be_ignored_list
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js
new file mode 100644
index 0000000..d7175dd
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+// License: GNU General Public License v3. See license.txt
+
+frappe.listview_settings['Transaction Deletion Record'] = {
+ get_indicator: function(doc) {
+ if (doc.docstatus == 0) {
+ return [__("Draft"), "red"];
+ } else {
+ return [__("Completed"), "green"];
+ }
+ }
+};
\ No newline at end of file
diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py b/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py
diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json
new file mode 100644
index 0000000..be0be94
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json
@@ -0,0 +1,39 @@
+{
+ "actions": [],
+ "creation": "2021-04-07 07:34:00.124124",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "doctype_name",
+ "no_of_docs"
+ ],
+ "fields": [
+ {
+ "fieldname": "doctype_name",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "DocType",
+ "options": "DocType",
+ "reqd": 1
+ },
+ {
+ "fieldname": "no_of_docs",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Number of Docs"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-08 23:10:46.166744",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Transaction Deletion Record Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py
new file mode 100644
index 0000000..2176cb1
--- /dev/null
+++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, 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 TransactionDeletionRecordItem(Document):
+ pass
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index f21d55f..7ae81d7 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -428,7 +428,6 @@
frappe.local.flags.ignore_update_nsm = True
make_records(records[1:])
frappe.local.flags.ignore_update_nsm = False
-
rebuild_tree("Department", "parent_department")
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 334bdea..7875b9c 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -273,11 +273,11 @@
},
items_on_form_rendered: function(doc, grid_row) {
- erpnext.setup_serial_no();
+ erpnext.setup_serial_or_batch_no();
},
packed_items_on_form_rendered: function(doc, grid_row) {
- erpnext.setup_serial_no();
+ erpnext.setup_serial_or_batch_no();
},
close_delivery_note: function(doc){
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index d39b229..0c63df0 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -710,7 +710,7 @@
dn1.submit()
si = make_sales_invoice(dn.name)
- self.assertEquals(si.items[0].qty, 1)
+ self.assertEqual(si.items[0].qty, 1)
def test_make_sales_invoice_from_dn_with_returned_qty_duplicate_items(self):
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
@@ -738,8 +738,8 @@
dn1.submit()
si2 = make_sales_invoice(dn.name)
- self.assertEquals(si2.items[0].qty, 2)
- self.assertEquals(si2.items[1].qty, 1)
+ self.assertEqual(si2.items[0].qty, 2)
+ self.assertEqual(si2.items[1].qty, 1)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js
index a6fbb66..68cba29 100755
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js
@@ -41,6 +41,15 @@
},
refresh: function (frm) {
+ if (frm.doc.docstatus == 1 && frm.doc.employee) {
+ frm.add_custom_button(__('Expense Claim'), function() {
+ frappe.model.open_mapped_doc({
+ method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.make_expense_claim',
+ frm: cur_frm,
+ });
+ }, __("Create"));
+ }
+
if (frm.doc.docstatus == 1 && frm.doc.delivery_stops.length > 0) {
frm.add_custom_button(__("Notify Customers via Email"), function () {
frm.trigger('notify_customers');
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
index 879901f..11b71c2 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json
@@ -21,6 +21,7 @@
"column_break_4",
"vehicle",
"departure_time",
+ "employee",
"delivery_service_stops",
"delivery_stops",
"calculate_arrival_time",
@@ -176,11 +177,19 @@
"fieldtype": "Data",
"label": "Driver Email",
"read_only": 1
+ },
+ {
+ "fetch_from": "driver.employee",
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "label": "Employee",
+ "options": "Employee",
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-01-26 22:37:14.824021",
+ "modified": "2021-04-30 21:21:36.610142",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Trip",
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index de85bc3..81e7301 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -11,6 +11,7 @@
from frappe.contacts.doctype.address.address import get_address_display
from frappe.model.document import Document
from frappe.utils import cint, get_datetime, get_link_to_form
+from frappe.model.mapper import get_mapped_doc
class DeliveryTrip(Document):
@@ -394,3 +395,15 @@
employee = frappe.db.get_value("Driver", driver, "employee")
email = frappe.db.get_value("Employee", employee, "prefered_email")
return {"email": email}
+
+@frappe.whitelist()
+def make_expense_claim(source_name, target_doc=None):
+ doc = get_mapped_doc("Delivery Trip", source_name,
+ {"Delivery Trip": {
+ "doctype": "Expense Claim",
+ "field_map": {
+ "name" : "delivery_trip"
+ }
+ }}, target_doc)
+
+ return doc
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
index eeea6da..1e71603 100644
--- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
@@ -7,7 +7,7 @@
import erpnext
import frappe
-from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers
+from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers, make_expense_claim
from erpnext.tests.utils import create_test_contact_and_address
from frappe.utils import add_days, flt, now_datetime, nowdate
@@ -28,6 +28,10 @@
frappe.db.sql("delete from `tabEmail Template`")
frappe.db.sql("delete from `tabDelivery Trip`")
+ def test_expense_claim_fields_are_fetched_properly(self):
+ expense_claim = make_expense_claim(self.delivery_trip.name)
+ self.assertEqual(self.delivery_trip.name, expense_claim.delivery_trip)
+
def test_delivery_trip_notify_customers(self):
notify_customers(delivery_trip=self.delivery_trip.name)
self.delivery_trip.load_from_db()
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 2079cf8..8aec893 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -46,9 +46,6 @@
}, __("View"));
}
- if (!frm.doc.is_fixed_asset) {
- erpnext.item.make_dashboard(frm);
- }
if (frm.doc.is_fixed_asset) {
frm.trigger('is_fixed_asset');
@@ -96,6 +93,10 @@
erpnext.item.edit_prices_button(frm);
erpnext.item.toggle_attributes(frm);
+
+ if (!frm.doc.is_fixed_asset) {
+ erpnext.item.make_dashboard(frm);
+ }
frm.add_custom_button(__('Duplicate'), function() {
var new_item = frappe.model.copy_doc(frm.doc);
@@ -473,11 +474,15 @@
me.multiple_variant_dialog.get_primary_btn().html(__('Create Variants'));
me.multiple_variant_dialog.disable_primary_action();
} else {
+
let no_of_combinations = lengths.reduce((a, b) => a * b, 1);
- me.multiple_variant_dialog.get_primary_btn()
- .html(__(
- `Make ${no_of_combinations} Variant${no_of_combinations === 1 ? '' : 's'}`
- ));
+ let msg;
+ if (no_of_combinations === 1) {
+ msg = __("Make {0} Variant", [no_of_combinations]);
+ } else {
+ msg = __("Make {0} Variants", [no_of_combinations]);
+ }
+ me.multiple_variant_dialog.get_primary_btn().html(msg);
me.multiple_variant_dialog.enable_primary_action();
}
}
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 7dfc5da..92c8d21 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -433,13 +433,21 @@
if (doc.material_request_type == "Customer Provided") {
return{
query: "erpnext.controllers.queries.item_query",
- filters:{ 'customer': me.frm.doc.customer }
+ filters:{
+ 'customer': me.frm.doc.customer,
+ 'is_stock_item':1
+ }
}
- } else if (doc.material_request_type != "Manufacture") {
+ } else if (doc.material_request_type == "Purchase") {
return{
query: "erpnext.controllers.queries.item_query",
filters: {'is_purchase_item': 1}
}
+ } else {
+ return{
+ query: "erpnext.controllers.queries.item_query",
+ filters: {'is_stock_item': 1}
+ }
}
});
},
diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json
index 8d7b238..4e2d9e6 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -181,7 +181,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nOrdered\nIssued\nTransferred\nReceived",
+ "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nPartially Received\nOrdered\nIssued\nTransferred\nReceived",
"print_hide": 1,
"print_width": "100px",
"read_only": 1,
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 4d1a514..befdad9 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -73,6 +73,34 @@
})
}, __('Create'));
}
+
+ frm.events.add_custom_buttons(frm);
+ },
+
+ add_custom_buttons: function(frm) {
+ if (frm.doc.docstatus == 0) {
+ frm.add_custom_button(__('Purchase Invoice'), function () {
+ if (!frm.doc.supplier) {
+ frappe.throw({
+ title: __("Mandatory"),
+ message: __("Please Select a Supplier")
+ });
+ }
+ erpnext.utils.map_current_doc({
+ method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
+ source_doctype: "Purchase Invoice",
+ target: frm,
+ setters: {
+ supplier: frm.doc.supplier,
+ },
+ get_query_filters: {
+ docstatus: 1,
+ per_received: ["<", 100],
+ company: frm.doc.company
+ }
+ })
+ }, __("Get Items From"));
+ }
},
company: function(frm) {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index d8d8310..61e60f3 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -53,7 +53,20 @@
'target_ref_field': 'stock_qty',
'source_field': 'stock_qty',
'percent_join_field': 'material_request'
+ },
+ {
+ 'source_dt': 'Purchase Receipt Item',
+ 'target_dt': 'Purchase Invoice Item',
+ 'join_field': 'purchase_invoice_item',
+ 'target_field': 'received_qty',
+ 'target_parent_dt': 'Purchase Invoice',
+ 'target_parent_field': 'per_received',
+ 'target_ref_field': 'qty',
+ 'source_field': 'received_qty',
+ 'percent_join_field': 'purchase_invoice',
+ 'overflow_type': 'receipt'
}]
+
if cint(self.is_return):
self.status_updater.extend([
{
@@ -514,7 +527,9 @@
def update_billing_status(self, update_modified=True):
updated_pr = [self.name]
for d in self.get("items"):
- if d.purchase_order_item:
+ if d.purchase_invoice and d.purchase_invoice_item:
+ d.db_set('billed_amt', d.amount, update_modified=update_modified)
+ elif d.purchase_order_item:
updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified)
for pr in set(updated_pr):
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 16eea24..e5ef978 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -13,8 +13,9 @@
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.item.test_item import make_item
from six import iteritems
+from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
-
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class TestPurchaseReceipt(unittest.TestCase):
def setUp(self):
@@ -144,6 +145,62 @@
self.assertFalse(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name}))
self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
+ def test_duplicate_serial_nos(self):
+ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+
+ item = frappe.db.exists("Item", {'item_name': 'Test Serialized Item 123'})
+ if not item:
+ item = create_item("Test Serialized Item 123")
+ item.has_serial_no = 1
+ item.serial_no_series = "TSI123-.####"
+ item.save()
+ else:
+ item = frappe.get_doc("Item", {'item_name': 'Test Serialized Item 123'})
+
+ # First make purchase receipt
+ pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500)
+ pr.load_from_db()
+
+ serial_nos = frappe.db.get_value('Stock Ledger Entry',
+ {'voucher_type': 'Purchase Receipt', 'voucher_no': pr.name, 'item_code': item.name}, 'serial_no')
+
+ serial_nos = get_serial_nos(serial_nos)
+
+ self.assertEquals(get_serial_nos(pr.items[0].serial_no), serial_nos)
+
+ # Then tried to receive same serial nos in difference company
+ pr_different_company = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
+ serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True,
+ warehouse = 'Stores - _TC1')
+
+ self.assertRaises(SerialNoDuplicateError, pr_different_company.submit)
+
+ # Then made delivery note to remove the serial nos from stock
+ dn = create_delivery_note(item_code=item.name, qty=2, rate = 1500, serial_no='\n'.join(serial_nos))
+ dn.load_from_db()
+ self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos)
+
+ posting_date = add_days(today(), -3)
+
+ # Try to receive same serial nos again in the same company with backdated.
+ pr1 = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
+ posting_date=posting_date, serial_no='\n'.join(serial_nos), do_not_submit=True)
+
+ self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit)
+
+ # Try to receive same serial nos with different company with backdated.
+ pr2 = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
+ posting_date=posting_date, serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True,
+ warehouse = 'Stores - _TC1')
+
+ self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit)
+
+ # Receive the same serial nos after the delivery note posting date and time
+ make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no='\n'.join(serial_nos))
+
+ # Raise the error for backdated deliver note entry cancel
+ self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel)
+
def test_purchase_receipt_gl_entry(self):
pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
@@ -562,30 +619,6 @@
new_pr_doc.cancel()
- def test_not_accept_duplicate_serial_no(self):
- from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
- from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
-
- item_code = frappe.db.get_value('Item', {'has_serial_no': 1, 'is_fixed_asset': 0, "has_batch_no": 0})
- if not item_code:
- item = make_item("Test Serial Item 1", dict(has_serial_no=1, has_batch_no=0))
- item_code = item.name
-
- serial_no = random_string(5)
- pr1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no)
- dn = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no)
-
- pr2 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True)
- self.assertRaises(SerialNoDuplicateError, pr2.submit)
-
- se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1,
- serial_no=serial_no, basic_rate=100, do_not_submit=True)
- se.submit()
-
- se.cancel()
- dn.cancel()
- pr1.cancel()
-
def test_auto_asset_creation(self):
asset_item = "Test Asset Item"
@@ -620,10 +653,10 @@
pr = make_purchase_receipt(item_code=asset_item, qty=3)
assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name})
- self.assertEquals(len(assets), 3)
+ self.assertEqual(len(assets), 3)
location = frappe.db.get_value('Asset', assets[0].name, 'location')
- self.assertEquals(location, "Test Location")
+ self.assertEqual(location, "Test Location")
pr.cancel()
@@ -728,7 +761,7 @@
pr1.submit()
pi = make_purchase_invoice(pr.name)
- self.assertEquals(pi.items[0].qty, 3)
+ self.assertEqual(pi.items[0].qty, 3)
pr1.cancel()
pr.reload()
@@ -759,8 +792,8 @@
pr2.submit()
pi2 = make_purchase_invoice(pr1.name)
- self.assertEquals(pi2.items[0].qty, 2)
- self.assertEquals(pi2.items[1].qty, 1)
+ self.assertEqual(pi2.items[0].qty, 2)
+ self.assertEqual(pi2.items[1].qty, 1)
pr2.cancel()
pi1.cancel()
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index efe3642..82cc98e 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -72,16 +72,18 @@
"warehouse",
"rejected_warehouse",
"from_warehouse",
- "purchase_order",
"material_request",
+ "purchase_order",
+ "purchase_invoice",
"column_break_40",
"is_fixed_asset",
"asset_location",
"asset_category",
"schedule_date",
"quality_inspection",
- "purchase_order_item",
"material_request_item",
+ "purchase_order_item",
+ "purchase_invoice_item",
"purchase_receipt_item",
"delivery_note_item",
"putaway_rule",
@@ -937,7 +939,21 @@
"fieldname": "base_rate_with_margin",
"fieldtype": "Currency",
"label": "Rate With Margin (Company Currency)",
- "options": "Company:company:default_currency",
+ "options": "Company:company:default_currency"
+ },
+ {
+ "fieldname": "purchase_invoice",
+ "fieldtype": "Link",
+ "label": "Purchase Invoice",
+ "options": "Purchase Invoice",
+ "read_only": 1
+ },
+ {
+ "fieldname": "purchase_invoice_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Purchase Invoice Item",
+ "no_copy": 1,
"print_hide": 1,
"read_only": 1
}
@@ -945,7 +961,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-02-23 00:59:14.360847",
+ "modified": "2021-03-29 04:17:00.336298",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index c02dd2e..5ecc9f8 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -243,7 +243,7 @@
if frappe.db.exists("Serial No", serial_no):
sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
"delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type",
- "purchase_document_no", "company"], as_dict=1)
+ "purchase_document_no", "company", "status"], as_dict=1)
if sr.item_code!=sle.item_code:
if not allow_serial_nos_with_different_item(serial_no, sle):
@@ -266,6 +266,9 @@
frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no,
sle.warehouse), SerialNoWarehouseError)
+ if not sr.purchase_document_no:
+ frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError)
+
if sle.voucher_type in ("Delivery Note", "Sales Invoice"):
if sr.batch_no and sr.batch_no != sle.batch_no:
@@ -382,19 +385,6 @@
if sn.company != sle.company:
return False
- status = False
- if sn.purchase_document_no:
- if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and
- sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]):
- status = True
-
- # If status is receipt then system will allow to in-ward the delivered serial no
- if (status and sle.voucher_type == "Stock Entry" and frappe.db.get_value("Stock Entry",
- sle.voucher_no, "purpose") in ("Material Receipt", "Material Transfer")):
- status = False
-
- return status
-
def allow_serial_nos_with_different_item(sle_serial_no, sle):
"""
Allows same serial nos for raw materials and finished goods
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index ef7d54a..772c8df 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -996,7 +996,7 @@
},
items_on_form_rendered: function(doc, grid_row) {
- erpnext.setup_serial_no();
+ erpnext.setup_serial_or_batch_no();
},
toggle_related_fields: function(doc) {
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 48cfa51..2f76bc7 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -76,6 +76,7 @@
self.validate_difference_account()
self.set_job_card_data()
self.set_purpose_for_stock_entry()
+ self.validate_duplicate_serial_no()
if not self.from_bom:
self.fg_completed_qty = 0.0
@@ -587,6 +588,22 @@
self.purpose = frappe.get_cached_value('Stock Entry Type',
self.stock_entry_type, 'purpose')
+ def validate_duplicate_serial_no(self):
+ warehouse_wise_serial_nos = {}
+
+ # In case of repack the source and target serial nos could be same
+ for warehouse in ['s_warehouse', 't_warehouse']:
+ serial_nos = []
+ for row in self.items:
+ if not (row.serial_no and row.get(warehouse)): continue
+
+ for sn in get_serial_nos(row.serial_no):
+ if sn in serial_nos:
+ frappe.throw(_('The serial no {0} has added multiple times in the stock entry {1}')
+ .format(frappe.bold(sn), self.name))
+
+ serial_nos.append(sn)
+
def validate_purchase_order(self):
"""Throw exception if more raw material is transferred against Purchase Order than in
the raw materials supplied table"""
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 1396f19..7e216d6 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -72,7 +72,7 @@
if item_dict.get("serial_nos"):
item.current_serial_no = item_dict.get("serial_nos")
- if self.purpose == "Stock Reconciliation":
+ if self.purpose == "Stock Reconciliation" and not item.serial_no:
item.serial_no = item.current_serial_no
item.current_qty = item_dict.get("qty")
@@ -469,7 +469,7 @@
def submit(self):
if len(self.items) > 100:
msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"))
- self.queue_action('submit')
+ self.queue_action('submit', timeout=2000)
else:
self._submit()
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index f18eabc..cf5d98d 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -5,40 +5,44 @@
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
+ "item_defaults_section",
"item_naming_by",
"item_group",
"stock_uom",
"default_warehouse",
- "sample_retention_warehouse",
"column_break_4",
"valuation_method",
+ "sample_retention_warehouse",
+ "use_naming_series",
+ "naming_series_prefix",
+ "section_break_9",
"over_delivery_receipt_allowance",
"role_allowed_to_over_deliver_receive",
- "action_if_quality_inspection_is_not_submitted",
- "show_barcode_field",
- "clean_description_html",
- "disable_serial_no_and_batch_selector",
- "section_break_7",
+ "column_break_12",
"auto_insert_price_list_rate_if_missing",
"allow_negative_stock",
- "column_break_10",
+ "show_barcode_field",
+ "clean_description_html",
+ "action_if_quality_inspection_is_not_submitted",
+ "section_break_7",
"automatically_set_serial_nos_based_on_fifo",
"set_qty_in_transactions_based_on_serial_no_input",
+ "column_break_10",
+ "disable_serial_no_and_batch_selector",
"auto_material_request",
"auto_indent",
+ "column_break_27",
"reorder_email_notify",
"inter_warehouse_transfer_settings_section",
"allow_from_dn",
+ "column_break_31",
"allow_from_pr",
"control_historical_stock_transactions_section",
- "role_allowed_to_create_edit_back_dated_transactions",
- "column_break_26",
"stock_frozen_upto",
"stock_frozen_upto_days",
- "stock_auth_role",
- "batch_id_sb",
- "use_naming_series",
- "naming_series_prefix"
+ "column_break_26",
+ "role_allowed_to_create_edit_back_dated_transactions",
+ "stock_auth_role"
],
"fields": [
{
@@ -102,23 +106,24 @@
"default": "1",
"fieldname": "show_barcode_field",
"fieldtype": "Check",
- "label": "Show Barcode Field"
+ "label": "Show Barcode Field in Stock Transactions"
},
{
"default": "1",
"fieldname": "clean_description_html",
"fieldtype": "Check",
- "label": "Convert Item Description to Clean HTML"
+ "label": "Convert Item Description to Clean HTML in Transactions"
},
{
"fieldname": "section_break_7",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Serialised and Batch Setting"
},
{
"default": "0",
"fieldname": "auto_insert_price_list_rate_if_missing",
"fieldtype": "Check",
- "label": "Auto Insert Price List Rate If Missing"
+ "label": "Auto Insert Item Price If Missing"
},
{
"default": "0",
@@ -180,15 +185,10 @@
"options": "Role"
},
{
- "fieldname": "batch_id_sb",
- "fieldtype": "Section Break",
- "label": "Batch Identification"
- },
- {
"default": "0",
"fieldname": "use_naming_series",
"fieldtype": "Check",
- "label": "Use Naming Series"
+ "label": "Have Default Naming Series for Batch ID?"
},
{
"default": "BATCH-",
@@ -242,6 +242,28 @@
"fieldtype": "Link",
"label": "Role Allowed to Over Deliver/Receive",
"options": "Role"
+ },
+ {
+ "fieldname": "item_defaults_section",
+ "fieldtype": "Section Break",
+ "label": "Item Defaults"
+ },
+ {
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break",
+ "label": "Stock Transactions Settings"
+ },
+ {
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_27",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_31",
+ "fieldtype": "Column Break"
}
],
"icon": "icon-cog",
@@ -249,7 +271,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-03-11 18:48:14.513055",
+ "modified": "2021-04-30 17:27:42.709231",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 3fc1df7..d1dcdc2 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -79,7 +79,7 @@
get_price_list_rate(args, item, out)
if args.customer and cint(args.is_pos):
- out.update(get_pos_profile_item_details(args.company, args))
+ out.update(get_pos_profile_item_details(args.company, args, update_data=True))
if (args.get("doctype") == "Material Request" and
args.get("material_request_type") == "Material Transfer"):
@@ -470,7 +470,9 @@
item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out)
item_group = item_group_doc.parent_item_group
-def _get_item_tax_template(args, taxes, out={}, for_validate=False):
+def _get_item_tax_template(args, taxes, out=None, for_validate=False):
+ if out is None:
+ out = {}
taxes_with_validity = []
taxes_with_no_validity = []
@@ -933,10 +935,10 @@
return bin_details
def get_company_total_stock(item_code, company):
- return frappe.db.sql("""SELECT sum(actual_qty) from
- (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name)
- WHERE `tabWarehouse`.company = '{0}' and `tabBin`.item_code = '{1}'"""
- .format(company, item_code))[0][0]
+ return frappe.db.sql("""SELECT sum(actual_qty) from
+ (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name)
+ WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""",
+ (company, item_code))[0][0]
@frappe.whitelist()
def get_serial_no_details(item_code, warehouse, stock_qty, serial_no):
diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
index 087c12e..01927c2 100644
--- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
+++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
@@ -70,7 +70,7 @@
return frappe.db.sql("""
select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty
from `tabStock Ledger Entry`
- where docstatus < 2 and ifnull(batch_no, '') != '' %s
+ where is_cancelled = 0 and docstatus < 2 and ifnull(batch_no, '') != '' %s
group by voucher_no, batch_no, item_code, warehouse
order by item_code, warehouse""" %
conditions, as_dict=1)
diff --git a/erpnext/stock/report/serial_no_ledger/__init__.py b/erpnext/stock/report/serial_no_ledger/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/serial_no_ledger/__init__.py
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js
new file mode 100644
index 0000000..616312e
--- /dev/null
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js
@@ -0,0 +1,52 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Serial No Ledger"] = {
+ "filters": [
+ {
+ 'label': __('Item Code'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'item_code',
+ 'reqd': 1,
+ 'options': 'Item',
+ get_query: function() {
+ return {
+ filters: {
+ 'has_serial_no': 1
+ }
+ }
+ }
+ },
+ {
+ 'label': __('Serial No'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'serial_no',
+ 'options': 'Serial No',
+ 'reqd': 1
+ },
+ {
+ 'label': __('Warehouse'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'warehouse',
+ 'options': 'Warehouse',
+ get_query: function() {
+ let company = frappe.query_report.get_filter_value('company');
+
+ if (company) {
+ return {
+ filters: {
+ 'company': company
+ }
+ }
+ }
+ }
+ },
+ {
+ 'label': __('As On Date'),
+ 'fieldtype': 'Date',
+ 'fieldname': 'posting_date',
+ 'default': frappe.datetime.get_today()
+ },
+ ]
+};
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json
new file mode 100644
index 0000000..e20e74c
--- /dev/null
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json
@@ -0,0 +1,33 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-04-20 13:32:41.523219",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "modified": "2021-04-20 13:33:19.015829",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Serial No Ledger",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "Serial No Ledger",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Sales User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
new file mode 100644
index 0000000..c3339fd
--- /dev/null
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+from frappe import _
+from erpnext.stock.stock_ledger import get_stock_ledger_entries
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+def execute(filters=None):
+ columns = get_columns(filters)
+ data = get_data(filters)
+ return columns, data
+
+def get_columns(filters):
+ columns = [{
+ 'label': _('Posting Date'),
+ 'fieldtype': 'Date',
+ 'fieldname': 'posting_date'
+ }, {
+ 'label': _('Posting Time'),
+ 'fieldtype': 'Time',
+ 'fieldname': 'posting_time'
+ }, {
+ 'label': _('Voucher Type'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'voucher_type',
+ 'options': 'DocType',
+ 'width': 220
+ }, {
+ 'label': _('Voucher No'),
+ 'fieldtype': 'Dynamic Link',
+ 'fieldname': 'voucher_no',
+ 'options': 'voucher_type',
+ 'width': 220
+ }, {
+ 'label': _('Company'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'company',
+ 'options': 'Company',
+ 'width': 220
+ }, {
+ 'label': _('Warehouse'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'warehouse',
+ 'options': 'Warehouse',
+ 'width': 220
+ }]
+
+ return columns
+
+def get_data(filters):
+ return get_stock_ledger_entries(filters, '<=', order="asc") or []
+
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 6dfede4..bbd73e9 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -165,7 +165,7 @@
select
sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
- sle.item_code as name, sle.voucher_no, sle.stock_value
+ sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no
from
`tabStock Ledger Entry` sle force index (posting_sort_index)
where sle.docstatus < 2 %s %s
@@ -193,7 +193,7 @@
qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)]
- if d.voucher_type == "Stock Reconciliation":
+ if d.voucher_type == "Stock Reconciliation" and not d.batch_no:
qty_diff = flt(d.qty_after_transaction) - flt(qty_dict.bal_qty)
else:
qty_diff = flt(d.actual_qty)
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
index babc6dc..cb109f8 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js
@@ -14,7 +14,14 @@
"fieldname":"warehouse",
"label": __("Warehouse"),
"fieldtype": "Link",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "get_query": () => {
+ return {
+ filters: {
+ company: frappe.query_report.get_filter_value('company')
+ }
+ }
+ }
},
{
"fieldname":"item_code",
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index 1183e41..808d279 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -6,6 +6,7 @@
from frappe import _
from frappe.utils import flt, today
from erpnext.stock.utils import update_included_uom_in_report, is_reposting_item_valuation_in_progress
+from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_pos_reserved_qty
def execute(filters=None):
is_reposting_item_valuation_in_progress()
@@ -49,9 +50,13 @@
if (re_order_level or re_order_qty) and re_order_level > bin.projected_qty:
shortage_qty = re_order_level - flt(bin.projected_qty)
+ reserved_qty_for_pos = get_pos_reserved_qty(bin.item_code, bin.warehouse)
+ if reserved_qty_for_pos:
+ bin.projected_qty -= reserved_qty_for_pos
+
data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty,
- bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract,
+ bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract, reserved_qty_for_pos,
bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
if include_uom:
@@ -74,9 +79,11 @@
{"label": _("Requested Qty"), "fieldname": "indented_qty", "fieldtype": "Float", "width": 110, "convertible": "qty"},
{"label": _("Ordered Qty"), "fieldname": "ordered_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Reserved Qty"), "fieldname": "reserved_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Reserved Qty for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float",
+ {"label": _("Reserved for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float",
"width": 100, "convertible": "qty"},
- {"label": _("Reserved for sub contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float",
+ {"label": _("Reserved for Sub Contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float",
+ "width": 100, "convertible": "qty"},
+ {"label": _("Reserved for POS Transactions"), "fieldname": "reserved_qty_for_pos", "fieldtype": "Float",
"width": 100, "convertible": "qty"},
{"label": _("Projected Qty"), "fieldname": "projected_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
{"label": _("Reorder Level"), "fieldname": "re_order_level", "fieldtype": "Float", "width": 100, "convertible": "qty"},
diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.py b/erpnext/stock/report/total_stock_summary/total_stock_summary.py
index ed52393..59c253c 100644
--- a/erpnext/stock/report/total_stock_summary/total_stock_summary.py
+++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.py
@@ -51,7 +51,7 @@
INNER JOIN `tabWarehouse` warehouse
ON warehouse.name = ledger.warehouse
WHERE
- actual_qty != 0 %s""" % (columns, conditions))
+ ledger.actual_qty != 0 %s""" % (columns, conditions))
def validate_filters(filters):
if filters.get("group_by") == 'Company' and \
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index bbfcb7a..9729987 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -2,9 +2,11 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe, erpnext
+import frappe
+import erpnext
+import copy
from frappe import _
-from frappe.utils import cint, flt, cstr, now, now_datetime
+from frappe.utils import cint, flt, cstr, now, get_link_to_form
from frappe.model.meta import get_field_precision
from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel
from erpnext.stock.utils import get_bin
@@ -13,6 +15,8 @@
# future reposting
class NegativeStockError(frappe.ValidationError): pass
+class SerialNoExistsInFutureTransaction(frappe.ValidationError):
+ pass
_exceptions = frappe.local('stockledger_exceptions')
# _exceptions = []
@@ -27,6 +31,9 @@
set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
for sle in sl_entries:
+ if sle.serial_no:
+ validate_serial_no(sle)
+
if cancel:
sle['actual_qty'] = -flt(sle.get('actual_qty'))
@@ -46,6 +53,30 @@
args = sle_doc.as_dict()
update_bin(args, allow_negative_stock, via_landed_cost_voucher)
+def validate_serial_no(sle):
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+ for sn in get_serial_nos(sle.serial_no):
+ args = copy.deepcopy(sle)
+ args.serial_no = sn
+ args.warehouse = ''
+
+ vouchers = []
+ for row in get_stock_ledger_entries(args, '>'):
+ voucher_type = frappe.bold(row.voucher_type)
+ voucher_no = frappe.bold(get_link_to_form(row.voucher_type, row.voucher_no))
+ vouchers.append(f'{voucher_type} {voucher_no}')
+
+ if vouchers:
+ serial_no = frappe.bold(sn)
+ msg = (f'''The serial no {serial_no} has been used in the future transactions so you need to cancel them first.
+ The list of the transactions are as below.''' + '<br><br><ul><li>')
+
+ msg += '</li><li>'.join(vouchers)
+ msg += '</li></ul>'
+
+ title = 'Cannot Submit' if not sle.get('is_cancelled') else 'Cannot Cancel'
+ frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransaction)
+
def validate_cancellation(args):
if args[0].get("is_cancelled"):
repost_entry = frappe.db.get_value("Repost Item Valuation", {
@@ -718,7 +749,17 @@
conditions += " and " + previous_sle.get("warehouse_condition")
if check_serial_no and previous_sle.get("serial_no"):
- conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no"))))
+ # conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no"))))
+ serial_no = previous_sle.get("serial_no")
+ conditions += (""" and
+ (
+ serial_no = {0}
+ or serial_no like {1}
+ or serial_no like {2}
+ or serial_no like {3}
+ )
+ """).format(frappe.db.escape(serial_no), frappe.db.escape('{}\n%'.format(serial_no)),
+ frappe.db.escape('%\n{}'.format(serial_no)), frappe.db.escape('%\n{}\n%'.format(serial_no)))
if not previous_sle.get("posting_date"):
previous_sle["posting_date"] = "1900-01-01"
@@ -793,12 +834,12 @@
if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \
and cint(erpnext.is_perpetual_inventory_enabled(company)):
frappe.local.message_log = []
- form_link = frappe.utils.get_link_to_form("Item", item_code)
+ form_link = get_link_to_form("Item", item_code)
message = _("Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.").format(form_link, voucher_type, voucher_no)
- message += "<br><br>" + _(" Here are the options to proceed:")
+ message += "<br><br>" + _("Here are the options to proceed:")
solutions = "<li>" + _("If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.").format(voucher_type) + "</li>"
- solutions += "<li>" + _("If not, you can Cancel / Submit this entry ") + _("{0}").format(frappe.bold("after")) + _(" performing either one below:") + "</li>"
+ solutions += "<li>" + _("If not, you can Cancel / Submit this entry") + " {0} ".format(frappe.bold("after")) + _("performing either one below:") + "</li>"
sub_solutions = "<ul><li>" + _("Create an incoming stock transaction for the Item.") + "</li>"
sub_solutions += "<li>" + _("Mention Valuation Rate in the Item master.") + "</li></ul>"
msg = message + solutions + sub_solutions + "</li>"
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index 46d02d8..7da5d7f 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -22,50 +22,50 @@
customer = create_customer("_Test Customer", "__Test SLA Customer Group", "__Test SLA Territory")
issue = make_issue(creation, "_Test Customer", 1)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
# make issue with customer_group specific SLA
customer = create_customer("__Test Customer", "_Test SLA Customer Group", "__Test SLA Territory")
issue = make_issue(creation, "__Test Customer", 2)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
# make issue with territory specific SLA
customer = create_customer("___Test Customer", "__Test SLA Customer Group", "_Test SLA Territory")
issue = make_issue(creation, "___Test Customer", 3)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 4, 15, 0))
# make issue with default SLA
issue = make_issue(creation=creation, index=4)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 16, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 4, 18, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 16, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 4, 18, 0))
# make issue with default SLA before working hours
creation = datetime.datetime(2019, 3, 4, 7, 0)
issue = make_issue(creation=creation, index=5)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 4, 16, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 14, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 4, 16, 0))
# make issue with default SLA after working hours
creation = datetime.datetime(2019, 3, 4, 20, 0)
issue = make_issue(creation, index=6)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 6, 14, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 6, 16, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 6, 14, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 6, 16, 0))
# make issue with default SLA next day
creation = datetime.datetime(2019, 3, 4, 14, 0)
issue = make_issue(creation=creation, index=7)
- self.assertEquals(issue.response_by, datetime.datetime(2019, 3, 4, 18, 0))
- self.assertEquals(issue.resolution_by, datetime.datetime(2019, 3, 6, 12, 0))
+ self.assertEqual(issue.response_by, datetime.datetime(2019, 3, 4, 18, 0))
+ self.assertEqual(issue.resolution_by, datetime.datetime(2019, 3, 6, 12, 0))
frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0)
diff --git a/requirements.txt b/requirements.txt
index 377fd7d..f1ffeb8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-frappe
+# frappe # https://github.com/frappe/frappe is installed during bench-init
gocardless-pro~=1.22.0
googlemaps # used in ERPNext, but dependency is defined in Frappe
pandas~=1.1.5