\ No newline at end of file
+
diff --git a/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.json b/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.json
index e3afaec2ad0..9e126bade7b 100644
--- a/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.json
+++ b/erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.json
@@ -16,7 +16,7 @@
"name": "Bank and Cash Payment Voucher",
"owner": "Administrator",
"print_format_builder": 0,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"show_section_headings": 0,
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/cheque_printing_format/cheque_printing_format.json b/erpnext/accounts/print_format/cheque_printing_format/cheque_printing_format.json
index 8c9c26691e5..08b8ef88900 100755
--- a/erpnext/accounts/print_format/cheque_printing_format/cheque_printing_format.json
+++ b/erpnext/accounts/print_format/cheque_printing_format/cheque_printing_format.json
@@ -10,6 +10,6 @@
"modified_by": "Administrator",
"name": "Cheque Printing Format",
"owner": "Administrator",
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/credit_note/credit_note.json b/erpnext/accounts/print_format/credit_note/credit_note.json
index c5a11cf3958..d12b872dd79 100644
--- a/erpnext/accounts/print_format/credit_note/credit_note.json
+++ b/erpnext/accounts/print_format/credit_note/credit_note.json
@@ -17,7 +17,7 @@
"owner": "Administrator",
"parentfield": "__print_formats",
"print_format_builder": 0,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"show_section_headings": 0,
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
index 71c26e8c55a..7643eca7635 100644
--- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
@@ -159,4 +159,4 @@
-
\ No newline at end of file
+
diff --git a/erpnext/accounts/print_format/gst_purchase_invoice/gst_purchase_invoice.json b/erpnext/accounts/print_format/gst_purchase_invoice/gst_purchase_invoice.json
index 6d7c3d31ae4..1467f27e66a 100644
--- a/erpnext/accounts/print_format/gst_purchase_invoice/gst_purchase_invoice.json
+++ b/erpnext/accounts/print_format/gst_purchase_invoice/gst_purchase_invoice.json
@@ -16,7 +16,7 @@
"name": "GST Purchase Invoice",
"owner": "Administrator",
"print_format_builder": 1,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"show_section_headings": 0,
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html
index 0ca940f8bd5..c1c611ee3a3 100644
--- a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html
+++ b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html
@@ -68,4 +68,4 @@
-
\ No newline at end of file
+
diff --git a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.json b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.json
index 927e818e017..e45295d9fea 100644
--- a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.json
+++ b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.json
@@ -16,7 +16,7 @@
"name": "Journal Auditing Voucher",
"owner": "Administrator",
"print_format_builder": 0,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"show_section_headings": 0,
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html
index 283d505e3be..ae07582704c 100644
--- a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html
+++ b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.html
@@ -27,4 +27,3 @@
{{ _("Authorized Signatory") }}
-
diff --git a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json
index f1de448bb98..c52eeb2dfb9 100755
--- a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json
+++ b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json
@@ -12,6 +12,6 @@
"name": "Payment Receipt Voucher",
"owner": "Administrator",
"print_format_builder": 0,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html b/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html
index 043ac254ed3..8696bffbfcb 100644
--- a/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html
+++ b/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.html
@@ -103,4 +103,4 @@
-
\ No newline at end of file
+
diff --git a/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.json b/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.json
index 73779d49aab..33981178220 100644
--- a/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.json
+++ b/erpnext/accounts/print_format/purchase_auditing_voucher/purchase_auditing_voucher.json
@@ -16,7 +16,7 @@
"name": "Purchase Auditing Voucher",
"owner": "Administrator",
"print_format_builder": 0,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"show_section_headings": 0,
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html b/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html
index a53b593a72a..efb2d00f0bf 100644
--- a/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html
+++ b/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.html
@@ -93,4 +93,4 @@
-
\ No newline at end of file
+
diff --git a/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.json b/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.json
index 0544e0bc9e8..289e1848488 100644
--- a/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.json
+++ b/erpnext/accounts/print_format/sales_auditing_voucher/sales_auditing_voucher.json
@@ -16,7 +16,7 @@
"name": "Sales Auditing Voucher",
"owner": "Administrator",
"print_format_builder": 0,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"show_section_headings": 0,
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/accounts/report/account_balance/test_account_balance.py b/erpnext/accounts/report/account_balance/test_account_balance.py
index 14ddf4a30fc..f5c9449e85d 100644
--- a/erpnext/accounts/report/account_balance/test_account_balance.py
+++ b/erpnext/accounts/report/account_balance/test_account_balance.py
@@ -62,8 +62,3 @@ def make_sales_invoice():
income_account = 'Sales - _TC2',
expense_account = 'Cost of Goods Sold - _TC2',
cost_center = 'Main - _TC2')
-
-
-
-
-
diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js
index 6abd6e5cf77..b6c6689be0b 100644
--- a/erpnext/accounts/report/accounts_payable/accounts_payable.js
+++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js
@@ -136,4 +136,3 @@ frappe.query_reports["Accounts Payable"] = {
}
erpnext.utils.add_dimensions('Accounts Payable', 9);
-
diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
index 9c6b0639c0a..ea200720dff 100644
--- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
+++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
@@ -105,4 +105,3 @@ frappe.query_reports["Accounts Payable Summary"] = {
}
erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
-
diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.py b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.py
index 729eda9492f..c08582b564c 100644
--- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.py
+++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.py
@@ -12,4 +12,3 @@ def execute(filters=None):
"naming_by": ["Buying Settings", "supp_master_name"],
}
return AccountsReceivableSummary(filters).run(args)
-
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index 29c4f7d3941..1a32e2a8e06 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -200,4 +200,3 @@ frappe.query_reports["Accounts Receivable"] = {
}
erpnext.utils.add_dimensions('Accounts Receivable', 9);
-
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index b54646fd27a..cedfc0f58b8 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -535,6 +535,8 @@ class ReceivablePayableReport(object):
if getdate(entry_date) > getdate(self.filters.report_date):
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
+ row.total_due = row.range1 + row.range2 + row.range3 + row.range4 + row.range5
+
def get_ageing_data(self, entry_date, row):
# [0-30, 30-60, 60-90, 90-120, 120-above]
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index 2ff5b531c51..cca67608238 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -93,4 +93,3 @@ def make_credit_note(docname):
cost_center = 'Main - _TC2',
is_return = 1,
return_against = docname)
-
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 657b3e8f204..4bfb022c4ee 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -82,6 +82,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"range3": 0.0,
"range4": 0.0,
"range5": 0.0,
+ "total_due": 0.0,
"sales_person": []
}))
@@ -134,4 +135,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]),
"{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
"{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]):
- self.add_column(label=label, fieldname='range' + str(i+1))
\ No newline at end of file
+ self.add_column(label=label, fieldname='range' + str(i+1))
+
+ # Add column for total due amount
+ self.add_column(label="Total Amount Due", fieldname='total_due')
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py
index 26bb44f4f7b..7838385dc56 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.py
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py
@@ -209,4 +209,4 @@ def get_chart_data(filters, columns, asset, liability, equity):
else:
chart["type"] = "line"
- return chart
\ No newline at end of file
+ return chart
diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.js b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.js
index dbee0229737..f0b6c6b20ac 100644
--- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.js
+++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.js
@@ -22,7 +22,7 @@ frappe.query_reports["Bank Clearance Summary"] = {
"fieldtype": "Link",
"options": "Account",
"reqd": 1,
- "default": frappe.defaults.get_user_default("Company")?
+ "default": frappe.defaults.get_user_default("Company")?
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "",
"get_query": function() {
return {
diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
index 79b0a6f30ec..95f724cc580 100644
--- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
+++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
@@ -74,19 +74,19 @@ def get_entries(filters):
journal_entries = frappe.db.sql("""SELECT
"Journal Entry", jv.name, jv.posting_date, jv.cheque_no,
jv.clearance_date, jvd.against_account, jvd.debit - jvd.credit
- FROM
+ FROM
`tabJournal Entry Account` jvd, `tabJournal Entry` jv
- WHERE
+ WHERE
jvd.parent = jv.name and jv.docstatus=1 and jvd.account = %(account)s {0}
order by posting_date DESC, jv.name DESC""".format(conditions), filters, as_list=1)
payment_entries = frappe.db.sql("""SELECT
- "Payment Entry", name, posting_date, reference_no, clearance_date, party,
+ "Payment Entry", name, posting_date, reference_no, clearance_date, party,
if(paid_from=%(account)s, paid_amount * -1, received_amount)
- FROM
+ FROM
`tabPayment Entry`
- WHERE
+ WHERE
docstatus=1 and (paid_from = %(account)s or paid_to = %(account)s) {0}
order by posting_date DESC, name DESC""".format(conditions), filters, as_list=1)
- return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate()))
\ No newline at end of file
+ return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate()))
diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js
index 8f028496cd5..9bb6a14c677 100644
--- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js
+++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js
@@ -16,7 +16,7 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
"label": __("Bank Account"),
"fieldtype": "Link",
"options": "Account",
- "default": frappe.defaults.get_user_default("Company")?
+ "default": frappe.defaults.get_user_default("Company")?
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "",
"reqd": 1,
"get_query": function() {
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
index 2ce5d50edf4..2dcea22f7e6 100644
--- 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
@@ -104,4 +104,4 @@ def get_columns():
'fieldtype': 'Currency',
'width': 100
}
- ]
\ No newline at end of file
+ ]
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js
index f547ca619bd..718b6e2fcb6 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js
@@ -92,4 +92,3 @@ frappe.query_reports["Budget Variance Report"] = {
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]);
});
-
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
index 9f0eee8aa5c..443126e4655 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
@@ -78,7 +78,7 @@ def get_final_data(dimension, dimension_items, filters, period_month_ranges, dat
if filters["period"] != "Yearly" :
row += totals
data.append(row)
-
+
return data
@@ -388,7 +388,7 @@ def get_chart_data(filters, columns, data):
budget_values[i] += values[index]
actual_values[i] += values[index+1]
index += 3
-
+
return {
'data': {
'labels': labels,
@@ -399,4 +399,3 @@ def get_chart_data(filters, columns, data):
},
'type' : 'bar'
}
-
diff --git a/erpnext/accounts/report/cash_flow/cash_flow.html b/erpnext/accounts/report/cash_flow/cash_flow.html
index 40ba20c4ac6..d4ae54d4f38 100644
--- a/erpnext/accounts/report/cash_flow/cash_flow.html
+++ b/erpnext/accounts/report/cash_flow/cash_flow.html
@@ -1 +1 @@
-{% include "accounts/report/financial_statements.html" %}
\ No newline at end of file
+{% include "accounts/report/financial_statements.html" %}
diff --git a/erpnext/accounts/report/cash_flow/cash_flow.js b/erpnext/accounts/report/cash_flow/cash_flow.js
index a984bf46b50..a2c34c6ee26 100644
--- a/erpnext/accounts/report/cash_flow/cash_flow.js
+++ b/erpnext/accounts/report/cash_flow/cash_flow.js
@@ -21,4 +21,4 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"default": 1
}
);
-});
\ No newline at end of file
+});
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 1363b53746a..6a8301a6f91 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
@@ -94,10 +94,10 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"default": 1
}
],
- "formatter": function(value, row, column, data, default_formatter) {
+ "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;
@@ -126,4 +126,4 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
});
}
}
-});
\ 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 56a67bb0989..fc4212733a3 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -210,10 +210,10 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
company_currency = get_company_currency(filters)
if filters.filter_based_on == 'Fiscal Year':
- start_date = fiscal_year.year_start_date
+ start_date = fiscal_year.year_start_date if filters.report != 'Balance Sheet' else None
end_date = fiscal_year.year_end_date
else:
- start_date = filters.period_start_date
+ start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None
end_date = filters.period_end_date
gl_entries_by_account = {}
diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
index 515fd995e66..9953d8fcaf5 100644
--- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
+++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
@@ -105,4 +105,4 @@ def get_column():
def get_args():
return {'doctype': 'Delivery Note', 'party': 'customer',
- 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'}
\ No newline at end of file
+ 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'}
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index 4a551b80124..095f5eda66a 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -176,4 +176,3 @@ frappe.query_reports["General Ledger"] = {
}
erpnext.utils.add_dimensions('General Ledger', 15)
-
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 1759fa3a48f..3723c8e0d23 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -48,7 +48,7 @@ def validate_filters(filters, account_details):
if not filters.get("from_date") and not filters.get("to_date"):
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
-
+
if filters.get('account'):
filters.account = frappe.parse_json(filters.get('account'))
for account in filters.account:
@@ -78,13 +78,10 @@ def validate_filters(filters, account_details):
def validate_party(filters):
party_type, party = filters.get("party_type"), filters.get("party")
- if party:
- if not party_type:
- frappe.throw(_("To filter based on Party, select Party Type first"))
- else:
- for d in party:
- if not frappe.db.exists(party_type, d):
- frappe.throw(_("Invalid {0}: {1}").format(party_type, d))
+ if party and party_type:
+ for d in party:
+ if not frappe.db.exists(party_type, d):
+ frappe.throw(_("Invalid {0}: {1}").format(party_type, d))
def set_account_currency(filters):
if filters.get("account") or (filters.get('party') and len(filters.party) == 1):
@@ -92,7 +89,7 @@ def set_account_currency(filters):
account_currency = None
if filters.get("account"):
- if len(filters.get("account")) == 1:
+ if len(filters.get("account")) == 1:
account_currency = get_account_currency(filters.account[0])
else:
currency = get_account_currency(filters.account[0])
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html
index 40ba20c4ac6..d4ae54d4f38 100644
--- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.html
@@ -1 +1 @@
-{% include "accounts/report/financial_statements.html" %}
\ No newline at end of file
+{% include "accounts/report/financial_statements.html" %}
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
index 714e48d279b..8e33af7ee8e 100644
--- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
@@ -165,4 +165,4 @@ def get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expe
has_value=True
if has_value:
- return profit_loss
\ No newline at end of file
+ return profit_loss
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.json b/erpnext/accounts/report/gross_profit/gross_profit.json
index cd6bac2d77d..5fff3fdba77 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.json
+++ b/erpnext/accounts/report/gross_profit/gross_profit.json
@@ -1,16 +1,20 @@
{
- "add_total_row": 1,
+ "add_total_row": 0,
+ "columns": [],
"creation": "2013-02-25 17:03:34",
+ "disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
+ "filters": [],
"idx": 3,
"is_standard": "Yes",
- "modified": "2020-08-13 11:26:39.112352",
+ "modified": "2021-08-19 18:57:07.468202",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Gross Profit",
"owner": "Administrator",
+ "prepared_report": 0,
"ref_doctype": "Sales Invoice",
"report_name": "Gross Profit",
"report_type": "Script Report",
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 6d8623c189d..c949d9b74e5 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -41,12 +41,14 @@ def execute(filters=None):
columns = get_columns(group_wise_columns, filters)
- for src in gross_profit_data.grouped_data:
+ for idx, src in enumerate(gross_profit_data.grouped_data):
row = []
for col in group_wise_columns.get(scrub(filters.group_by)):
row.append(src.get(col))
row.append(filters.currency)
+ if idx == len(gross_profit_data.grouped_data)-1:
+ row[0] = frappe.bold("Total")
data.append(row)
return columns, data
@@ -154,6 +156,15 @@ class GrossProfitGenerator(object):
def get_average_rate_based_on_group_by(self):
# sum buying / selling totals for group
+ self.totals = frappe._dict(
+ qty=0,
+ base_amount=0,
+ buying_amount=0,
+ gross_profit=0,
+ gross_profit_percent=0,
+ base_rate=0,
+ buying_rate=0
+ )
for key in list(self.grouped):
if self.filters.get("group_by") != "Invoice":
for i, row in enumerate(self.grouped[key]):
@@ -165,6 +176,7 @@ class GrossProfitGenerator(object):
new_row.base_amount += flt(row.base_amount, self.currency_precision)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)
+ self.add_to_totals(new_row)
else:
for i, row in enumerate(self.grouped[key]):
if row.parent in self.returned_invoices \
@@ -177,15 +189,25 @@ class GrossProfitGenerator(object):
if row.qty or row.base_amount:
row = self.set_average_rate(row)
self.grouped_data.append(row)
+ self.add_to_totals(row)
+ self.set_average_gross_profit(self.totals)
+ self.grouped_data.append(self.totals)
def set_average_rate(self, new_row):
+ self.set_average_gross_profit(new_row)
+ new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
+ new_row.base_rate = flt(new_row.base_amount / new_row.qty, self.float_precision) if new_row.qty else 0
+ return new_row
+
+ def set_average_gross_profit(self, new_row):
new_row.gross_profit = flt(new_row.base_amount - new_row.buying_amount, self.currency_precision)
new_row.gross_profit_percent = flt(((new_row.gross_profit / new_row.base_amount) * 100.0), self.currency_precision) \
if new_row.base_amount else 0
- new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
- new_row.base_rate = flt(new_row.base_amount / new_row.qty, self.float_precision) if new_row.qty else 0
- return new_row
+ def add_to_totals(self, new_row):
+ for key in self.totals:
+ if new_row.get(key):
+ self.totals[key] += new_row[key]
def get_returned_invoice_items(self):
returned_invoices = frappe.db.sql("""
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index 2e794da8425..c9c22c246ed 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -76,7 +76,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
'company': d.company,
'sales_order': d.sales_order,
'delivery_note': d.delivery_note,
- 'income_account': d.unrealized_profit_loss_account or d.income_account,
+ 'income_account': d.unrealized_profit_loss_account if d.is_internal_customer == 1 else d.income_account,
'cost_center': d.cost_center,
'stock_qty': d.stock_qty,
'stock_uom': d.stock_uom
@@ -380,6 +380,7 @@ def get_items(filters, additional_query_columns):
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.unrealized_profit_loss_account,
+ `tabSales Invoice`.is_internal_customer,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
@@ -625,7 +626,3 @@ def add_sub_total_row(item, total_row_map, group_by_value, tax_columns):
for tax in tax_columns:
total_row.setdefault(frappe.scrub(tax + ' Amount'), 0.0)
total_row[frappe.scrub(tax + ' Amount')] += flt(item[frappe.scrub(tax + ' Amount')])
-
-
-
-
diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py
index 2e18ce11ddc..51735056896 100644
--- a/erpnext/accounts/report/non_billed_report.py
+++ b/erpnext/accounts/report/non_billed_report.py
@@ -44,4 +44,4 @@ def get_ordered_to_be_billed_data(args):
def get_project_field(doctype, party):
if party == "supplier": doctype = doctype + ' Item'
- return "`tab%s`.project"%(doctype)
\ No newline at end of file
+ return "`tab%s`.project"%(doctype)
diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
index 7195c7e0b8b..556f5ad4f79 100644
--- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
+++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
@@ -40,7 +40,7 @@ def execute(filters=None):
row = [
d.voucher_type, d.voucher_no, d.party_type, d.party, d.posting_date, d.against_voucher,
- invoice.posting_date, invoice.due_date, d.debit, d.credit, d.remarks,
+ invoice.posting_date, invoice.due_date, d.debit, d.credit, d.remarks,
d.age, d.range1, d.range2, d.range3, d.range4
]
diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py
index 6a42bb4fb65..b7e112c0c9a 100644
--- a/erpnext/accounts/report/pos_register/pos_register.py
+++ b/erpnext/accounts/report/pos_register/pos_register.py
@@ -10,7 +10,7 @@ from erpnext.accounts.report.sales_register.sales_register import get_mode_of_pa
def execute(filters=None):
if not filters:
return [], []
-
+
validate_filters(filters)
columns = get_columns(filters)
@@ -29,7 +29,7 @@ def execute(filters=None):
invoice_map, grouped_data = {}, []
for d in pos_entries:
invoice_map.setdefault(d[group_by_field], []).append(d)
-
+
for key in invoice_map:
invoices = invoice_map[key]
grouped_data += invoices
@@ -56,7 +56,7 @@ def get_pos_entries(filters, group_by_field):
return frappe.db.sql(
"""
- SELECT
+ SELECT
p.posting_date, p.name as pos_invoice, p.pos_profile,
p.owner, p.base_grand_total as grand_total, p.base_paid_amount as paid_amount,
p.customer, p.is_return {select_mop_field}
@@ -96,22 +96,22 @@ def add_subtotal_row(data, group_invoices, group_by_field, group_by_value):
def validate_filters(filters):
if not filters.get("company"):
frappe.throw(_("{0} is mandatory").format(_("Company")))
-
+
if not filters.get("from_date") and not filters.get("to_date"):
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
-
+
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
if (filters.get("pos_profile") and filters.get("group_by") == _('POS Profile')):
frappe.throw(_("Can not filter based on POS Profile, if grouped by POS Profile"))
-
+
if (filters.get("customer") and filters.get("group_by") == _('Customer')):
frappe.throw(_("Can not filter based on Customer, if grouped by Customer"))
-
+
if (filters.get("owner") and filters.get("group_by") == _('Cashier')):
frappe.throw(_("Can not filter based on Cashier, if grouped by Cashier"))
-
+
if (filters.get("mode_of_payment") and filters.get("group_by") == _('Payment Method')):
frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method"))
@@ -120,23 +120,23 @@ def get_conditions(filters):
if filters.get("pos_profile"):
conditions += " AND pos_profile = %(pos_profile)s"
-
+
if filters.get("owner"):
conditions += " AND owner = %(owner)s"
-
+
if filters.get("customer"):
conditions += " AND customer = %(customer)s"
-
+
if filters.get("is_return"):
conditions += " AND is_return = %(is_return)s"
-
+
if filters.get("mode_of_payment"):
conditions += """
AND EXISTS(
SELECT name FROM `tabSales Invoice Payment` sip
WHERE parent=p.name AND ifnull(sip.mode_of_payment, '') = %(mode_of_payment)s
)"""
-
+
return conditions
def get_group_by_field(group_by):
@@ -150,7 +150,7 @@ def get_group_by_field(group_by):
group_by_field = "customer"
elif group_by == "Payment Method":
group_by_field = "mode_of_payment"
-
+
return group_by_field
def get_columns(filters):
@@ -217,4 +217,4 @@ def get_columns(filters):
},
]
- return columns
\ No newline at end of file
+ return columns
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.html b/erpnext/accounts/report/profitability_analysis/profitability_analysis.html
index 40ba20c4ac6..d4ae54d4f38 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.html
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.html
@@ -1 +1 @@
-{% include "accounts/report/financial_statements.html" %}
\ No newline at end of file
+{% include "accounts/report/financial_statements.html" %}
diff --git a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js
index a95cfacaeef..feab96f2652 100644
--- a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js
+++ b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.js
@@ -5,4 +5,4 @@ frappe.require("assets/erpnext/js/purchase_trends_filters.js", function() {
frappe.query_reports["Purchase Invoice Trends"] = {
filters: erpnext.get_purchase_trends_filters()
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py
index ad3783f0de1..ba236b9969d 100644
--- a/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py
+++ b/erpnext/accounts/report/purchase_invoice_trends/purchase_invoice_trends.py
@@ -11,4 +11,4 @@ def execute(filters=None):
conditions = get_columns(filters, "Purchase Invoice")
data = get_data(filters, conditions)
- return conditions["columns"], data
\ No newline at end of file
+ return conditions["columns"], data
diff --git a/erpnext/accounts/report/purchase_register/purchase_register.js b/erpnext/accounts/report/purchase_register/purchase_register.js
index f34ea571639..aaf76c42997 100644
--- a/erpnext/accounts/report/purchase_register/purchase_register.js
+++ b/erpnext/accounts/report/purchase_register/purchase_register.js
@@ -56,4 +56,4 @@ frappe.query_reports["Purchase Register"] = {
]
}
-erpnext.utils.add_dimensions('Purchase Register', 7);
\ No newline at end of file
+erpnext.utils.add_dimensions('Purchase Register', 7);
diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
index e9e9c9c4e69..a5eced5f80b 100644
--- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
+++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
@@ -105,4 +105,4 @@ def get_column():
def get_args():
return {'doctype': 'Purchase Receipt', 'party': 'supplier',
- 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'}
\ No newline at end of file
+ 'date': 'posting_date', 'order': 'name', 'order_by': 'desc'}
diff --git a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js
index 2d320f52cfa..e3d43a7de1e 100644
--- a/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js
+++ b/erpnext/accounts/report/sales_invoice_trends/sales_invoice_trends.js
@@ -5,4 +5,4 @@ frappe.require("assets/erpnext/js/sales_trends_filters.js", function() {
frappe.query_reports["Sales Invoice Trends"] = {
filters: erpnext.get_sales_trends_filters()
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js
index 068926b063d..44e20e83c50 100644
--- a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js
+++ b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js
@@ -42,4 +42,4 @@ frappe.query_reports["Sales Payment Summary"] = {
"fieldtype": "Check"
},
]
-};
\ No newline at end of file
+};
diff --git a/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py b/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py
index a51c4276301..e4a3d3527fd 100644
--- a/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py
+++ b/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py
@@ -162,4 +162,4 @@ def create_records():
"price_list": "Standard Selling",
"item_code": item.item_code,
"price_list_rate": 10000
- }).insert()
\ No newline at end of file
+ }).insert()
diff --git a/erpnext/accounts/report/sales_register/sales_register.js b/erpnext/accounts/report/sales_register/sales_register.js
index 85bbceab827..2c9b01bbaa3 100644
--- a/erpnext/accounts/report/sales_register/sales_register.js
+++ b/erpnext/accounts/report/sales_register/sales_register.js
@@ -69,4 +69,3 @@ frappe.query_reports["Sales Register"] = {
}
erpnext.utils.add_dimensions('Sales Register', 7);
-
diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py
index 909959323f7..f38bd78c0d2 100644
--- a/erpnext/accounts/report/sales_register/sales_register.py
+++ b/erpnext/accounts/report/sales_register/sales_register.py
@@ -84,7 +84,7 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
# Add amount in unrealized account
for account in unrealized_profit_loss_accounts:
row.update({
- frappe.scrub(account): flt(internal_invoice_map.get((inv.name, account)))
+ frappe.scrub(account+"_unrealized"): flt(internal_invoice_map.get((inv.name, account)))
})
# net total
@@ -258,6 +258,7 @@ def get_columns(invoice_list, additional_table_columns):
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
from `tabSales Invoice` where docstatus = 1 and name in (%s)
+ and is_internal_customer = 1
and ifnull(unrealized_profit_loss_account, '') != ''
order by unrealized_profit_loss_account""" %
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
@@ -284,7 +285,7 @@ def get_columns(invoice_list, additional_table_columns):
for account in unrealized_profit_loss_accounts:
unrealized_profit_loss_account_columns.append({
"label": account,
- "fieldname": frappe.scrub(account),
+ "fieldname": frappe.scrub(account+"_unrealized"),
"fieldtype": "Currency",
"options": "currency",
"width": 120
diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py
index d2c23ee4e78..fbd25b13bb5 100644
--- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py
+++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.py
@@ -10,4 +10,4 @@ def execute(filters=None):
"party_type": "Supplier",
"naming_by": ["Buying Settings", "supp_master_name"],
}
- return PartyLedgerSummaryReport(filters).run(args)
\ No newline at end of file
+ return PartyLedgerSummaryReport(filters).run(args)
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js
index 8645d55d0fe..078b06519f1 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.js
+++ b/erpnext/accounts/report/trial_balance/trial_balance.js
@@ -110,6 +110,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
erpnext.utils.add_dimensions('Trial Balance', 6);
});
-
-
-
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 33360e2b01e..1fc0faab3a7 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -321,4 +321,4 @@ def prepare_opening_closing(row):
row[reverse_col] = abs(row[valid_col])
row[valid_col] = 0.0
else:
- row[reverse_col] = 0.0
\ No newline at end of file
+ row[reverse_col] = 0.0
diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
index 78c7e439d38..f034e7450ee 100644
--- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
+++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
@@ -242,4 +242,4 @@ def is_party_name_visible(filters):
else:
show_party_name = True
- return show_party_name
\ No newline at end of file
+ return show_party_name
diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py
index eee620b7cc8..1250d676a06 100644
--- a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py
+++ b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py
@@ -20,11 +20,11 @@ def get_unclaimed_expese_claims(filters):
if filters.get("employee"):
cond = "ec.employee = %(employee)s"
- return frappe.db.sql("""
+ return frappe.db.sql("""
select
ec.employee, ec.employee_name, ec.name, ec.total_sanctioned_amount, ec.total_amount_reimbursed,
sum(gle.credit_in_account_currency - gle.debit_in_account_currency) as outstanding_amt
- from
+ from
`tabExpense Claim` ec, `tabGL Entry` gle
where
gle.against_voucher_type = "Expense Claim" and gle.against_voucher = ec.name
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 9afe365f747..9120602adf2 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -19,6 +19,7 @@ from erpnext.stock import get_warehouse_account_map
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
class FiscalYearError(frappe.ValidationError): pass
+class PaymentEntryUnlinkError(frappe.ValidationError): pass
@frappe.whitelist()
def get_fiscal_year(date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False):
@@ -350,6 +351,7 @@ def reconcile_against_document(args):
# cancel advance entry
doc = frappe.get_doc(d.voucher_type, d.voucher_no)
+ frappe.flags.ignore_party_validation = True
doc.make_gl_entries(cancel=1, adv_adj=1)
# update ref in advance entry
@@ -361,6 +363,7 @@ def reconcile_against_document(args):
# re-submit advance entry
doc = frappe.get_doc(d.voucher_type, d.voucher_no)
doc.make_gl_entries(cancel = 0, adv_adj =1)
+ frappe.flags.ignore_party_validation = False
if d.voucher_type in ('Payment Entry', 'Journal Entry'):
doc.update_expense_claim()
@@ -553,10 +556,16 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
for pe in linked_pe:
- pe_doc = frappe.get_doc("Payment Entry", pe)
- pe_doc.set_total_allocated_amount()
- pe_doc.set_unallocated_amount()
- pe_doc.clear_unallocated_reference_document_rows()
+ try:
+ pe_doc = frappe.get_doc("Payment Entry", pe)
+ pe_doc.set_amounts()
+ pe_doc.clear_unallocated_reference_document_rows()
+ pe_doc.validate_payment_type_with_outstanding()
+ except Exception as e:
+ msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name)
+ msg += ' '
+ msg += _("Please cancel payment entry manually first")
+ frappe.throw(msg, exc=PaymentEntryUnlinkError, title=_("Payment Unlink Error"))
frappe.db.sql("""update `tabPayment Entry` set total_allocated_amount=%s,
base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s
@@ -920,7 +929,6 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa
_delete_gl_entries(voucher_type, voucher_no)
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None):
- future_stock_vouchers = []
values = []
condition = ""
@@ -936,30 +944,46 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f
condition += " and company = %s"
values.append(company)
- for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
+ future_stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where
timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s)
and is_cancelled = 0
{condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
- tuple([posting_date, posting_time] + values), as_dict=True):
- future_stock_vouchers.append([d.voucher_type, d.voucher_no])
+ tuple([posting_date, posting_time] + values), as_dict=True)
- return future_stock_vouchers
+ return [(d.voucher_type, d.voucher_no) for d in future_stock_vouchers]
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
+ """ Get voucherwise list of GL entries.
+
+ Only fetches GLE fields required for comparing with new GLE.
+ Check compare_existing_and_expected_gle function below.
+ """
gl_entries = {}
- if future_stock_vouchers:
- for d in frappe.db.sql("""select * from `tabGL Entry`
- where posting_date >= %s and voucher_no in (%s)""" %
- ('%s', ', '.join(['%s']*len(future_stock_vouchers))),
- tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
- gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
+ if not future_stock_vouchers:
+ return gl_entries
+
+ voucher_nos = [d[1] for d in future_stock_vouchers]
+
+ gles = frappe.db.sql("""
+ select name, account, credit, debit, cost_center, project
+ from `tabGL Entry`
+ where
+ posting_date >= %s and voucher_no in (%s)""" %
+ ('%s', ', '.join(['%s'] * len(voucher_nos))),
+ tuple([posting_date] + voucher_nos), as_dict=1)
+
+ for d in gles:
+ gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries
def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
+ if len(existing_gle) != len(expected_gle):
+ return False
+
matched = True
for entry in expected_gle:
account_existed = False
@@ -1062,3 +1086,14 @@ def get_journal_entry(account, stock_adjustment_account, amount):
db_or_cr_stock_adjustment_account : abs(amount)
}]
}
+
+def check_and_delete_linked_reports(report):
+ """ Check if reports are referenced in Desktop Icon """
+ icons = frappe.get_all("Desktop Icon",
+ fields = ['name'],
+ filters = {
+ "_report": report
+ })
+ if icons:
+ for icon in icons:
+ frappe.delete_doc("Desktop Icon", icon)
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index 10a4001502f..7e3ecaf3ab6 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -588,7 +588,7 @@
{
"hidden": 0,
"is_query_report": 0,
- "label": "Bank Statement",
+ "label": "Banking and Payments",
"onboard": 0,
"type": "Card Break"
},
@@ -642,6 +642,24 @@
"onboard": 0,
"type": "Link"
},
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Payment Entry",
+ "link_to": "Payment Entry",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Payment Reconciliation",
+ "link_to": "Payment Reconciliation",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
{
"hidden": 0,
"is_query_report": 0,
@@ -1064,7 +1082,7 @@
"type": "Link"
}
],
- "modified": "2021-06-10 03:17:31.427945",
+ "modified": "2021-08-23 16:06:34.167267",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
diff --git a/erpnext/agriculture/doctype/crop/crop.js b/erpnext/agriculture/doctype/crop/crop.js
index afd84fd9f66..550824636b6 100644
--- a/erpnext/agriculture/doctype/crop/crop.js
+++ b/erpnext/agriculture/doctype/crop/crop.js
@@ -52,4 +52,4 @@ erpnext.crop.update_item_qty_amount = function(frm, cdt, cdn) {
}
});
});
-};
\ No newline at end of file
+};
diff --git a/erpnext/agriculture/doctype/crop/crop_dashboard.py b/erpnext/agriculture/doctype/crop/crop_dashboard.py
index 9a8f26fe90c..8f37735c812 100644
--- a/erpnext/agriculture/doctype/crop/crop_dashboard.py
+++ b/erpnext/agriculture/doctype/crop/crop_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Crop Cycle']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/agriculture/doctype/crop/test_crop.js b/erpnext/agriculture/doctype/crop/test_crop.js
index 138acbf85a3..40555634a2c 100644
--- a/erpnext/agriculture/doctype/crop/test_crop.js
+++ b/erpnext/agriculture/doctype/crop/test_crop.js
@@ -105,7 +105,7 @@ QUnit.test("test: Crop", function (assert) {
]
]}
]),
- // agriculture task list
+ // agriculture task list
() => {
assert.equal(cur_frm.doc.name, 'Basil from seed');
assert.equal(cur_frm.doc.period, 15);
diff --git a/erpnext/agriculture/doctype/crop/test_crop.py b/erpnext/agriculture/doctype/crop/test_crop.py
index c2e49174047..b3079837c35 100644
--- a/erpnext/agriculture/doctype/crop/test_crop.py
+++ b/erpnext/agriculture/doctype/crop/test_crop.py
@@ -11,4 +11,4 @@ test_dependencies = ["Fertilizer"]
class TestCrop(unittest.TestCase):
def test_crop_period(self):
basil = frappe.get_doc('Crop', 'Basil from seed')
- self.assertEqual(basil.period, 15)
\ No newline at end of file
+ self.assertEqual(basil.period, 15)
diff --git a/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.js b/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.js
index 464a3680baa..87184daedc9 100644
--- a/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.js
+++ b/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.js
@@ -19,7 +19,7 @@ QUnit.test("test: Crop Cycle", function (assert) {
{disease: 'Aphids'}
]
]},
- {linked_land_unit: [
+ {linked_land_unit: [
[
{land_unit: 'Basil Farm'}
]
diff --git a/erpnext/agriculture/doctype/disease/disease.py b/erpnext/agriculture/doctype/disease/disease.py
index c7707a54652..affa57046e5 100644
--- a/erpnext/agriculture/doctype/disease/disease.py
+++ b/erpnext/agriculture/doctype/disease/disease.py
@@ -17,4 +17,4 @@ class Disease(Document):
frappe.throw(_("Start day is greater than end day in task '{0}'").format(task.task_name))
# to calculate the period of the Crop Cycle
if task.end_day > max_period: max_period = task.end_day
- self.treatment_period = max_period
\ No newline at end of file
+ self.treatment_period = max_period
diff --git a/erpnext/agriculture/doctype/disease/test_disease.js b/erpnext/agriculture/doctype/disease/test_disease.js
index 57d62c16c25..33f60c4e152 100644
--- a/erpnext/agriculture/doctype/disease/test_disease.js
+++ b/erpnext/agriculture/doctype/disease/test_disease.js
@@ -36,4 +36,3 @@ QUnit.test("test: Disease", function (assert) {
]);
});
-
diff --git a/erpnext/agriculture/doctype/disease/test_disease.py b/erpnext/agriculture/doctype/disease/test_disease.py
index 54788a2c817..80861770b0d 100644
--- a/erpnext/agriculture/doctype/disease/test_disease.py
+++ b/erpnext/agriculture/doctype/disease/test_disease.py
@@ -9,4 +9,4 @@ import unittest
class TestDisease(unittest.TestCase):
def test_treatment_period(self):
disease = frappe.get_doc('Disease', 'Aphids')
- self.assertEqual(disease.treatment_period, 3)
\ No newline at end of file
+ self.assertEqual(disease.treatment_period, 3)
diff --git a/erpnext/agriculture/doctype/fertilizer/fertilizer.py b/erpnext/agriculture/doctype/fertilizer/fertilizer.py
index 9cb492aff1e..c475f002981 100644
--- a/erpnext/agriculture/doctype/fertilizer/fertilizer.py
+++ b/erpnext/agriculture/doctype/fertilizer/fertilizer.py
@@ -11,4 +11,4 @@ class Fertilizer(Document):
def load_contents(self):
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Fertilizer'})
for doc in docs:
- self.append('fertilizer_contents', {'title': str(doc.name)})
\ No newline at end of file
+ self.append('fertilizer_contents', {'title': str(doc.name)})
diff --git a/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py b/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py
index 3a25b3f0a7a..4c71d33fe80 100644
--- a/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py
+++ b/erpnext/agriculture/doctype/fertilizer/test_fertilizer.py
@@ -8,4 +8,4 @@ import unittest
class TestFertilizer(unittest.TestCase):
def test_fertilizer_creation(self):
- self.assertEqual(frappe.db.exists('Fertilizer', 'Urea'), 'Urea')
\ No newline at end of file
+ self.assertEqual(frappe.db.exists('Fertilizer', 'Urea'), 'Urea')
diff --git a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py
index 2806cc6523e..b65f93de0a0 100644
--- a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py
+++ b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py
@@ -12,4 +12,4 @@ class PlantAnalysis(Document):
def load_contents(self):
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Plant Analysis'})
for doc in docs:
- self.append('plant_analysis_criteria', {'title': str(doc.name)})
\ No newline at end of file
+ self.append('plant_analysis_criteria', {'title': str(doc.name)})
diff --git a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py
index 37835f8c7b1..234d0d4b011 100644
--- a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py
+++ b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py
@@ -11,4 +11,4 @@ class SoilAnalysis(Document):
def load_contents(self):
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Analysis'})
for doc in docs:
- self.append('soil_analysis_criteria', {'title': str(doc.name)})
\ No newline at end of file
+ self.append('soil_analysis_criteria', {'title': str(doc.name)})
diff --git a/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py b/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py
index 937c06ccadf..16d105c9c58 100644
--- a/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py
+++ b/erpnext/agriculture/doctype/soil_texture/test_soil_texture.py
@@ -11,4 +11,4 @@ class TestSoilTexture(unittest.TestCase):
soil_tex = frappe.get_all('Soil Texture', fields=['name'], filters={'collection_datetime': '2017-11-08'})
doc = frappe.get_doc('Soil Texture', soil_tex[0].name)
self.assertEqual(doc.silt_composition, 50)
- self.assertEqual(doc.soil_type, 'Silt Loam')
\ No newline at end of file
+ self.assertEqual(doc.soil_type, 'Silt Loam')
diff --git a/erpnext/agriculture/doctype/water_analysis/water_analysis.py b/erpnext/agriculture/doctype/water_analysis/water_analysis.py
index d9f007cea13..cb2691d4555 100644
--- a/erpnext/agriculture/doctype/water_analysis/water_analysis.py
+++ b/erpnext/agriculture/doctype/water_analysis/water_analysis.py
@@ -24,4 +24,4 @@ class WaterAnalysis(Document):
if self.collection_datetime > self.laboratory_testing_datetime:
frappe.throw(_('Lab testing datetime cannot be before collection datetime'))
if self.laboratory_testing_datetime > self.result_datetime:
- frappe.throw(_('Lab result datetime cannot be before testing datetime'))
\ No newline at end of file
+ frappe.throw(_('Lab result datetime cannot be before testing datetime'))
diff --git a/erpnext/agriculture/setup.py b/erpnext/agriculture/setup.py
index ab91343d5d1..75f07be5de2 100644
--- a/erpnext/agriculture/setup.py
+++ b/erpnext/agriculture/setup.py
@@ -426,5 +426,5 @@ def create_agriculture_data():
title='Degree Days',
standard=1,
linked_doctype='Weather')
- ]
+ ]
insert_record(records)
diff --git a/erpnext/assets/dashboard_fixtures.py b/erpnext/assets/dashboard_fixtures.py
index 7f3c1de406a..2c701796072 100644
--- a/erpnext/assets/dashboard_fixtures.py
+++ b/erpnext/assets/dashboard_fixtures.py
@@ -176,4 +176,4 @@ def get_number_cards(fiscal_year, year_start_date, year_end_date):
"filters_json": "[]",
"doctype": "Number Card"
}
- ]
\ No newline at end of file
+ ]
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 922cc4a7b26..da5778ea3d5 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -103,11 +103,11 @@ frappe.ui.form.on('Asset', {
frm.trigger("create_asset_maintenance");
}, __("Manage"));
}
-
+
frm.add_custom_button(__("Repair Asset"), function() {
frm.trigger("create_asset_repair");
}, __("Manage"));
-
+
if (frm.doc.status != 'Fully Depreciated') {
frm.add_custom_button(__("Adjust Asset Value"), function() {
frm.trigger("create_asset_adjustment");
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index ecc35b05b35..d955430f23c 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -176,16 +176,16 @@ class Asset(AccountsController):
for d in self.get('finance_books'):
self.validate_asset_finance_books(d)
-
+
start = self.clear_depreciation_schedule()
# value_after_depreciation - current Asset value
if d.value_after_depreciation:
value_after_depreciation = (flt(d.value_after_depreciation) -
- flt(self.opening_accumulated_depreciation))
+ flt(self.opening_accumulated_depreciation))
else:
value_after_depreciation = (flt(self.gross_purchase_amount) -
- flt(self.opening_accumulated_depreciation))
+ flt(self.opening_accumulated_depreciation))
d.value_after_depreciation = value_after_depreciation
diff --git a/erpnext/assets/doctype/asset/asset_dashboard.py b/erpnext/assets/doctype/asset/asset_dashboard.py
index a5cf23803d2..62bb4be53aa 100644
--- a/erpnext/assets/doctype/asset/asset_dashboard.py
+++ b/erpnext/assets/doctype/asset/asset_dashboard.py
@@ -11,4 +11,4 @@ def get_data():
'items': ['Asset Movement']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/assets/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js
index 02f39e0e7f4..4302cb2c518 100644
--- a/erpnext/assets/doctype/asset/asset_list.js
+++ b/erpnext/assets/doctype/asset/asset_list.js
@@ -50,4 +50,4 @@ frappe.listview_settings['Asset'] = {
});
});
},
-}
\ No newline at end of file
+}
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 8f0afb42b2c..251fe3fa493 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -59,7 +59,7 @@ def make_depreciation_entry(asset_name, date=None):
"credit_in_account_currency": d.depreciation_amount,
"reference_type": "Asset",
"reference_name": asset.name,
- "cost_center": ""
+ "cost_center": depreciation_cost_center
}
debit_entry = {
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index e23a7154524..605ce2e2503 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -763,4 +763,4 @@ def set_depreciation_settings_in_company():
company.save()
# Enable booking asset depreciation entry automatically
- frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
\ No newline at end of file
+ frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py
index 46620d56e98..39032d637b5 100644
--- a/erpnext/assets/doctype/asset_category/asset_category.py
+++ b/erpnext/assets/doctype/asset_category/asset_category.py
@@ -20,7 +20,7 @@ class AssetCategory(Document):
for field in ("Total Number of Depreciations", "Frequency of Depreciation"):
if cint(d.get(frappe.scrub(field)))<1:
frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError)
-
+
def validate_account_currency(self):
account_types = [
'fixed_asset_account', 'accumulated_depreciation_account', 'depreciation_expense_account', 'capital_work_in_progress_account'
@@ -33,13 +33,13 @@ class AssetCategory(Document):
account_currency = frappe.get_value("Account", d.get(type_of_account), "account_currency")
if account_currency != company_currency:
invalid_accounts.append(frappe._dict({ 'type': type_of_account, 'idx': d.idx, 'account': d.get(type_of_account) }))
-
+
for d in invalid_accounts:
frappe.throw(_("Row #{}: Currency of {} - {} doesn't matches company currency.")
.format(d.idx, frappe.bold(frappe.unscrub(d.type)), frappe.bold(d.account)),
title=_("Invalid Account"))
-
+
def validate_account_types(self):
account_type_map = {
'fixed_asset_account': { 'account_type': 'Fixed Asset' },
@@ -59,12 +59,12 @@ class AssetCategory(Document):
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
title=_("Invalid Account"))
-
+
def valide_cwip_account(self):
if self.enable_cwip_accounting:
missing_cwip_accounts_for_company = []
for d in self.accounts:
- if (not d.capital_work_in_progress_account and
+ if (not d.capital_work_in_progress_account and
not frappe.db.get_value("Company", d.company_name, "capital_work_in_progress_account")):
missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name))
@@ -93,4 +93,4 @@ def get_asset_category_account(fieldname, item=None, asset=None, account=None, a
account = frappe.db.get_value("Asset Category Account",
filters={"parent": asset_category, "company_name": company}, fieldname=fieldname)
- return account
\ No newline at end of file
+ return account
diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.py b/erpnext/assets/doctype/asset_category/test_asset_category.py
index 39b79d6c507..9f7ada65d82 100644
--- a/erpnext/assets/doctype/asset_category/test_asset_category.py
+++ b/erpnext/assets/doctype/asset_category/test_asset_category.py
@@ -10,9 +10,9 @@ class TestAssetCategory(unittest.TestCase):
def test_mandatory_fields(self):
asset_category = frappe.new_doc("Asset Category")
asset_category.asset_category_name = "Computers"
-
+
self.assertRaises(frappe.MandatoryError, asset_category.insert)
-
+
asset_category.total_number_of_depreciations = 3
asset_category.frequency_of_depreciation = 3
asset_category.append("accounts", {
@@ -21,7 +21,7 @@ class TestAssetCategory(unittest.TestCase):
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
"depreciation_expense_account": "_Test Depreciations - _TC"
})
-
+
try:
asset_category.insert()
except frappe.DuplicateEntryError:
@@ -44,4 +44,4 @@ class TestAssetCategory(unittest.TestCase):
"depreciation_expense_account": "_Test Depreciations - _TC"
})
- self.assertRaises(frappe.ValidationError, asset_category.insert)
\ No newline at end of file
+ self.assertRaises(frappe.ValidationError, asset_category.insert)
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js
index 70b8654509f..52996e93475 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js
@@ -97,4 +97,4 @@ var get_next_due_date = function (frm, cdt, cdn) {
}
});
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
index a506deec93e..e14f1d88dcb 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
@@ -116,4 +116,4 @@ def get_maintenance_log(asset_name):
select maintenance_status, count(asset_name) as count, asset_name
from `tabAsset Maintenance Log`
where asset_name=%s group by maintenance_status""",
- (asset_name), as_dict=1)
\ No newline at end of file
+ (asset_name), as_dict=1)
diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
index 392fbdd2af7..7610152039d 100644
--- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
@@ -73,7 +73,7 @@ def create_asset_data():
'doctype': 'Location',
'location_name': 'Test Location'
}).insert()
-
+
if not frappe.db.exists("Item", "Photocopier"):
meta = frappe.get_meta('Asset')
naming_series = meta.get_field("naming_series").options
@@ -157,6 +157,6 @@ def set_depreciation_settings_in_company():
company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC"
company.depreciation_cost_center = "_Test Cost Center - _TC"
company.save()
-
+
# Enable booking asset depreciation entry automatically
- frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
\ No newline at end of file
+ frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js
index c5db90ad370..bcdc3acf0ac 100644
--- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js
+++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.js
@@ -12,4 +12,4 @@ frappe.ui.form.on('Asset Maintenance Log', {
};
});
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.js b/erpnext/assets/doctype/asset_movement/asset_movement.js
index 06d8879091c..2df7db97446 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.js
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.js
@@ -99,4 +99,4 @@ frappe.ui.form.on('Asset Movement Item', {
});
}
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py
index b2de250b168..1771e27ddfe 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.py
@@ -40,14 +40,14 @@ class AssetMovement(Document):
if current_location != d.source_location:
frappe.throw(_("Asset {0} does not belongs to the location {1}").
format(d.asset, d.source_location))
-
+
if self.purpose == 'Issue':
if d.target_location:
frappe.throw(_("Issuing cannot be done to a location. \
Please enter employee who has issued Asset {0}").format(d.asset), title="Incorrect Movement Purpose")
if not d.to_employee:
frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset))
-
+
if self.purpose == 'Transfer':
if d.to_employee:
frappe.throw(_("Transferring cannot be done to an Employee. \
@@ -57,7 +57,7 @@ class AssetMovement(Document):
frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset))
if d.source_location == d.target_location:
frappe.throw(_("Source and Target Location cannot be same"))
-
+
if self.purpose == 'Receipt':
# only when asset is bought and first entry is made
if not d.source_location and not (d.target_location or d.to_employee):
@@ -80,14 +80,14 @@ class AssetMovement(Document):
if current_custodian != d.from_employee:
frappe.throw(_("Asset {0} does not belongs to the custodian {1}").
format(d.asset, d.from_employee))
-
+
if d.to_employee and frappe.db.get_value("Employee", d.to_employee, "company") != self.company:
frappe.throw(_("Employee {0} does not belongs to the company {1}").
format(d.to_employee, self.company))
def on_submit(self):
self.set_latest_location_in_asset()
-
+
def on_cancel(self):
self.set_latest_location_in_asset()
@@ -105,12 +105,12 @@ class AssetMovement(Document):
# In case of cancellation it corresponds to previous latest document's location, employee
latest_movement_entry = frappe.db.sql(
"""
- SELECT asm_item.target_location, asm_item.to_employee
+ SELECT asm_item.target_location, asm_item.to_employee
FROM `tabAsset Movement Item` asm_item, `tabAsset Movement` asm
- WHERE
+ WHERE
asm_item.parent=asm.name and
asm_item.asset=%(asset)s and
- asm.company=%(company)s and
+ asm.company=%(company)s and
asm.docstatus=1 and {0}
ORDER BY
asm.transaction_date desc limit 1
diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py
index cddee5fa0f1..2b2d2b44004 100644
--- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py
@@ -15,6 +15,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
class TestAssetMovement(unittest.TestCase):
def setUp(self):
+ frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC")
create_asset_data()
make_location()
@@ -45,12 +46,12 @@ class TestAssetMovement(unittest.TestCase):
'location_name': 'Test Location 2'
}).insert()
- movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
+ movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}],
reference_doctype = 'Purchase Receipt', reference_name = pr.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
- movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company,
+ create_asset_movement(purpose = 'Transfer', company = asset.company,
assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}],
reference_doctype = 'Purchase Receipt', reference_name = pr.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
@@ -59,18 +60,18 @@ class TestAssetMovement(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
employee = make_employee("testassetmovemp@example.com", company="_Test Company")
- movement3 = create_asset_movement(purpose = 'Issue', company = asset.company,
+ create_asset_movement(purpose = 'Issue', company = asset.company,
assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}],
reference_doctype = 'Purchase Receipt', reference_name = pr.name)
-
+
# after issuing asset should belong to an employee not at a location
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee)
-
+
def test_last_movement_cancellation(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=100000.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
@@ -85,17 +86,17 @@ class TestAssetMovement(unittest.TestCase):
})
if asset.docstatus == 0:
asset.submit()
-
+
if not frappe.db.exists("Location", "Test Location 2"):
frappe.get_doc({
'doctype': 'Location',
'location_name': 'Test Location 2'
}).insert()
-
+
movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name })
self.assertRaises(frappe.ValidationError, movement.cancel)
- movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
+ movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company,
assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}],
reference_doctype = 'Purchase Receipt', reference_name = pr.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js
index 1cebfff66e5..18a56d33e6d 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.js
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.js
@@ -59,7 +59,7 @@ frappe.ui.form.on('Asset Repair', {
if (frm.doc.repair_status == "Completed") {
frm.set_value('completion_date', frappe.datetime.now_datetime());
- }
+ }
}
});
@@ -68,4 +68,4 @@ frappe.ui.form.on('Asset Repair Consumed Item', {
var row = locals[cdt][cdn];
frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate);
},
-});
\ No newline at end of file
+});
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index d32fdf7054f..746f582fdcd 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -18,7 +18,7 @@ class AssetRepair(AccountsController):
if self.get('stock_items'):
self.set_total_value()
self.calculate_total_repair_cost()
-
+
def update_status(self):
if self.repair_status == 'Pending':
frappe.db.set_value('Asset', self.asset, 'status', 'Out of Order')
@@ -98,7 +98,7 @@ class AssetRepair(AccountsController):
if self.capitalize_repair_cost:
row.value_after_depreciation -= self.repair_cost
-
+
def get_total_value_of_stock_consumed(self):
total_value_of_stock_consumed = 0
if self.get('stock_consumption'):
@@ -141,7 +141,7 @@ class AssetRepair(AccountsController):
gl_entries = []
repair_and_maintenance_account = frappe.db.get_value('Company', self.company, 'repair_and_maintenance_account')
fixed_asset_account = get_asset_account("fixed_asset_account", asset=self.asset, company=self.company)
- expense_account = frappe.get_doc('Purchase Invoice', self.purchase_invoice).items[0].expense_account
+ expense_account = frappe.get_doc('Purchase Invoice', self.purchase_invoice).items[0].expense_account
gl_entries.append(
self.get_gl_dict({
@@ -149,7 +149,7 @@ class AssetRepair(AccountsController):
"credit": self.repair_cost,
"credit_in_account_currency": self.repair_cost,
"against": repair_and_maintenance_account,
- "voucher_type": self.doctype,
+ "voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
@@ -167,7 +167,7 @@ class AssetRepair(AccountsController):
"credit": item.amount,
"credit_in_account_currency": item.amount,
"against": repair_and_maintenance_account,
- "voucher_type": self.doctype,
+ "voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair_list.js b/erpnext/assets/doctype/asset_repair/asset_repair_list.js
index f36fd2f8dcb..86376f40046 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair_list.js
+++ b/erpnext/assets/doctype/asset_repair/asset_repair_list.js
@@ -10,4 +10,3 @@ frappe.listview_settings['Asset Repair'] = {
}
}
};
-
diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
index 30bbb37851e..5e727d007a9 100644
--- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
@@ -41,7 +41,7 @@ class TestAssetRepair(unittest.TestCase):
self.assertEqual(total_repair_cost, asset_repair.repair_cost)
for item in asset_repair.stock_items:
total_repair_cost += item.total_value
-
+
self.assertEqual(total_repair_cost, asset_repair.total_repair_cost)
def test_repair_status_after_submit(self):
@@ -99,7 +99,7 @@ class TestAssetRepair(unittest.TestCase):
initial_num_of_depreciations = num_of_depreciations(asset)
create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
asset.reload()
-
+
self.assertEqual((initial_num_of_depreciations + 1), num_of_depreciations(asset))
self.assertEqual(asset.schedules[-1].accumulated_depreciation_amount, asset.finance_books[0].value_after_depreciation)
@@ -139,7 +139,7 @@ def create_asset_repair(**args):
})
asset_repair.insert(ignore_if_duplicate=True)
-
+
if args.submit:
asset_repair.repair_status = "Completed"
asset_repair.cost_center = "_Test Cost Center - _TC"
@@ -165,4 +165,4 @@ def create_asset_repair(**args):
asset_repair.purchase_invoice = make_purchase_invoice().name
asset_repair.submit()
- return asset_repair
\ No newline at end of file
+ return asset_repair
diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py
index 03dc47b0bba..a9dc9795ee3 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py
@@ -91,4 +91,4 @@ def make_asset_value_adjustment(**args):
"cost_center": args.cost_center or "Main - _TC"
}).insert()
- return doc
\ No newline at end of file
+ return doc
diff --git a/erpnext/assets/doctype/location/location_tree.js b/erpnext/assets/doctype/location/location_tree.js
index b405afd1ddd..3e105f6ca49 100644
--- a/erpnext/assets/doctype/location/location_tree.js
+++ b/erpnext/assets/doctype/location/location_tree.js
@@ -30,4 +30,4 @@ frappe.treeview_settings["Location"] = {
onload: function (treeview) {
treeview.make_tree();
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
index 1a6ef54a830..75f42a9f783 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
@@ -76,7 +76,7 @@ frappe.query_reports["Fixed Asset Register"] = {
fieldtype: "Link",
options: "Asset Category"
},
- {
+ {
fieldname:"finance_book",
label: __("Finance Book"),
fieldtype: "Link",
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index d1457b9b85a..7d07397944b 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -99,7 +99,7 @@ def prepare_chart_data(data, filters):
labels_values_map = {}
date_field = frappe.scrub(filters.date_based_on)
- period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
+ period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
filters.from_date, filters.to_date, filters.filter_based_on, "Monthly", company=filters.company)
for d in period_list:
@@ -293,4 +293,4 @@ def get_columns(filters):
"options": "Location",
"width": 100
},
- ]
\ No newline at end of file
+ ]
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.js b/erpnext/buying/doctype/buying_settings/buying_settings.js
index e496e9628d1..944bb61cfeb 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.js
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.js
@@ -28,4 +28,4 @@ frappe.tour['Buying Settings'] = [
title: "Purchase Receipt Required for Purchase Invoice Creation",
description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Receipt' checkbox in the Supplier master.")
}
-];
\ No newline at end of file
+];
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index bb0ad60cabc..a55a0b7f9fc 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -565,6 +565,7 @@
"fieldname": "scan_barcode",
"fieldtype": "Data",
"label": "Scan Barcode",
+ "options": "Barcode",
"show_days": 1,
"show_seconds": 1
},
@@ -1378,7 +1379,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2021-05-30 15:17:53.663648",
+ "modified": "2021-08-17 20:16:12.737743",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index a0b1e073cc6..ca3bd90960c 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -635,4 +635,4 @@ def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None):
'item_code': row.item_details['rm_item_code'],
'subcontracted_item': row.item_details['main_item_code'],
'serial_no': '\n'.join(row.serial_no) if row.serial_no else ''
- })
\ No newline at end of file
+ })
diff --git a/erpnext/buying/doctype/purchase_order/regional/india.js b/erpnext/buying/doctype/purchase_order/regional/india.js
index 42d3995907f..ef83f203e73 100644
--- a/erpnext/buying/doctype/purchase_order/regional/india.js
+++ b/erpnext/buying/doctype/purchase_order/regional/india.js
@@ -1,3 +1,3 @@
{% include "erpnext/regional/india/taxes.js" %}
-erpnext.setup_auto_gst_taxation('Purchase Order');
\ No newline at end of file
+erpnext.setup_auto_gst_taxation('Purchase Order');
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index d668c76b6b9..fa174ba8fa8 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -485,7 +485,7 @@ class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_invoice_with_terms(self):
from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules
-
+
automatically_fetch_payment_terms()
po = create_purchase_order(do_not_save=True)
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js
index 5d196874c98..012b0619cc9 100644
--- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js
@@ -77,4 +77,4 @@ QUnit.test("test: purchase order", function(assert) {
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js
index 8c0c1443144..bc3d767f95d 100644
--- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js
@@ -58,4 +58,4 @@ QUnit.test("test: purchase order with get items", function(assert) {
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js
index 4e73ab8ef4f..83eb295010a 100644
--- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js
@@ -44,4 +44,4 @@ QUnit.test("test: purchase order with discount on grand total", function(assert)
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js
index 1e54e50dda9..a729dd9839f 100644
--- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js
@@ -41,4 +41,4 @@ QUnit.test("test: purchase order with item wise discount", function(assert) {
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js
index bf2dfeb37b7..b605e76ddf4 100644
--- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js
@@ -36,4 +36,4 @@ QUnit.test("test: purchase order with multi UOM", function(assert) {
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_shipping_rule.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_shipping_rule.js
index 96775eb0075..c258756b2a1 100644
--- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_shipping_rule.js
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_shipping_rule.js
@@ -40,4 +40,4 @@ QUnit.test("test: purchase order with shipping rule", function(assert) {
() => frappe.timeout(0.3),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js
index 39716ed560c..ccc383fd74e 100644
--- a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js
@@ -41,4 +41,4 @@ QUnit.test("test: purchase order with taxes and charges", function(assert) {
() => frappe.timeout(0.3),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
index 8bdcd47e028..b6e28b6c674 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
@@ -10,4 +10,4 @@ class PurchaseOrderItem(Document):
pass
def on_doctype_update():
- frappe.db.add_index("Purchase Order Item", ["item_code", "warehouse"])
\ No newline at end of file
+ frappe.db.add_index("Purchase Order Item", ["item_code", "warehouse"])
diff --git a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.py b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.py
index 6caffbda1f3..c85ca2fbafc 100644
--- a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.py
+++ b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class PurchaseOrderItemSupplied(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.py b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.py
index 1a76f0ee7db..00c93ed1ea3 100644
--- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.py
+++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class PurchaseReceiptItemSupplied(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index a4ce84e1cf9..8ed6c9e2a6d 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -425,4 +425,4 @@ def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filt
.format(filters.get("supplier"), filters.get("company"), conditions),
{"page_len": page_len, "start": start}, as_dict=1)
- return rfq_data
\ No newline at end of file
+ return rfq_data
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py
index 6efbc782252..751336dc4c6 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation_dashboard.py
@@ -10,4 +10,4 @@ def get_data():
'items': ['Supplier Quotation']
},
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js
index 1fcfe75bb03..75f85f86d1d 100644
--- a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js
@@ -73,4 +73,4 @@ QUnit.test("test: request_for_quotation", function(assert) {
() => frappe.click_button('Close'),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js
index 2e1652de733..f06c3f34c44 100644
--- a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js
+++ b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js
@@ -125,4 +125,4 @@ QUnit.test("Test: Request for Quotation", function (assert) {
},
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/supplier/regional/india.js b/erpnext/buying/doctype/supplier/regional/india.js
index bd710e0df71..5f49a47e75e 100644
--- a/erpnext/buying/doctype/supplier/regional/india.js
+++ b/erpnext/buying/doctype/supplier/regional/india.js
@@ -1,3 +1,3 @@
{% include "erpnext/regional/india/party.js" %}
-erpnext.setup_gst_reminder_button('Supplier');
\ No newline at end of file
+erpnext.setup_gst_reminder_button('Supplier');
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 1766c2c80cc..7ee91961ca5 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -24,7 +24,26 @@ frappe.ui.form.on("Supplier", {
}
}
});
+
+ frm.set_query("supplier_primary_contact", function(doc) {
+ return {
+ query: "erpnext.buying.doctype.supplier.supplier.get_supplier_primary_contact",
+ filters: {
+ "supplier": doc.name
+ }
+ };
+ });
+
+ frm.set_query("supplier_primary_address", function(doc) {
+ return {
+ filters: {
+ "link_doctype": "Supplier",
+ "link_name": doc.name
+ }
+ };
+ });
},
+
refresh: function (frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Supplier' }
@@ -78,6 +97,30 @@ frappe.ui.form.on("Supplier", {
});
},
+ supplier_primary_address: function(frm) {
+ if (frm.doc.supplier_primary_address) {
+ frappe.call({
+ method: 'frappe.contacts.doctype.address.address.get_address_display',
+ args: {
+ "address_dict": frm.doc.supplier_primary_address
+ },
+ callback: function(r) {
+ frm.set_value("primary_address", r.message);
+ }
+ });
+ }
+ if (!frm.doc.supplier_primary_address) {
+ frm.set_value("primary_address", "");
+ }
+ },
+
+ supplier_primary_contact: function(frm) {
+ if (!frm.doc.supplier_primary_contact) {
+ frm.set_value("mobile_no", "");
+ frm.set_value("email_id", "");
+ }
+ },
+
is_internal_supplier: function(frm) {
if (frm.doc.is_internal_supplier == 1) {
frm.toggle_reqd("represents_company", true);
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index 38b8dfdf48d..c7a5db59941 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -49,6 +49,13 @@
"address_html",
"column_break1",
"contact_html",
+ "primary_address_and_contact_detail_section",
+ "supplier_primary_contact",
+ "mobile_no",
+ "email_id",
+ "column_break_44",
+ "supplier_primary_address",
+ "primary_address",
"default_payable_accounts",
"accounts",
"default_tax_withholding_config",
@@ -378,6 +385,47 @@
"fieldname": "allow_purchase_invoice_creation_without_purchase_receipt",
"fieldtype": "Check",
"label": "Allow Purchase Invoice Creation Without Purchase Receipt"
+ },
+ {
+ "fieldname": "primary_address_and_contact_detail_section",
+ "fieldtype": "Section Break",
+ "label": "Primary Address and Contact Detail"
+ },
+ {
+ "description": "Reselect, if the chosen contact is edited after save",
+ "fieldname": "supplier_primary_contact",
+ "fieldtype": "Link",
+ "label": "Supplier Primary Contact",
+ "options": "Contact"
+ },
+ {
+ "fetch_from": "supplier_primary_contact.mobile_no",
+ "fieldname": "mobile_no",
+ "fieldtype": "Read Only",
+ "label": "Mobile No"
+ },
+ {
+ "fetch_from": "supplier_primary_contact.email_id",
+ "fieldname": "email_id",
+ "fieldtype": "Read Only",
+ "label": "Email Id"
+ },
+ {
+ "fieldname": "column_break_44",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "primary_address",
+ "fieldtype": "Text",
+ "label": "Primary Address",
+ "read_only": 1
+ },
+ {
+ "description": "Reselect, if the chosen address is edited after save",
+ "fieldname": "supplier_primary_address",
+ "fieldtype": "Link",
+ "label": "Supplier Primary Address",
+ "options": "Address"
}
],
"icon": "fa fa-user",
@@ -390,7 +438,7 @@
"link_fieldname": "supplier"
}
],
- "modified": "2021-05-18 15:10:11.087191",
+ "modified": "2021-08-27 18:02:44.314077",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index fd16b23c220..c9750caa65a 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -42,7 +42,12 @@ class Supplier(TransactionBase):
if not self.naming_series:
self.naming_series = ''
+ self.create_primary_contact()
+ self.create_primary_address()
+
def validate(self):
+ self.flags.is_new_doc = self.is_new()
+
# validation for Naming Series mandatory field...
if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series':
if not self.naming_series:
@@ -76,7 +81,39 @@ class Supplier(TransactionBase):
frappe.throw(_("Internal Supplier for company {0} already exists").format(
frappe.bold(self.represents_company)))
+ def create_primary_contact(self):
+ from erpnext.selling.doctype.customer.customer import make_contact
+
+ if not self.supplier_primary_contact:
+ if self.mobile_no or self.email_id:
+ contact = make_contact(self)
+ self.db_set('supplier_primary_contact', contact.name)
+ self.db_set('mobile_no', self.mobile_no)
+ self.db_set('email_id', self.email_id)
+
+ def create_primary_address(self):
+ from erpnext.selling.doctype.customer.customer import make_address
+ from frappe.contacts.doctype.address.address import get_address_display
+
+ if self.flags.is_new_doc and self.get('address_line1'):
+ address = make_address(self)
+ address_display = get_address_display(address.name)
+
+ self.db_set("supplier_primary_address", address.name)
+ self.db_set("primary_address", address_display)
+
def on_trash(self):
+ if self.supplier_primary_contact:
+ frappe.db.sql("""
+ UPDATE `tabSupplier`
+ SET
+ supplier_primary_contact=null,
+ supplier_primary_address=null,
+ mobile_no=null,
+ email_id=null,
+ primary_address=null
+ WHERE name=%(name)s""", {"name": self.name})
+
delete_contact_and_address('Supplier', self.name)
def after_rename(self, olddn, newdn, merge=False):
@@ -104,3 +141,21 @@ class Supplier(TransactionBase):
doc.name, args.get('supplier_email_' + str(i)))
except frappe.NameError:
pass
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters):
+ supplier = filters.get("supplier")
+ return frappe.db.sql("""
+ SELECT
+ `tabContact`.name from `tabContact`,
+ `tabDynamic Link`
+ WHERE
+ `tabContact`.name = `tabDynamic Link`.parent
+ and `tabDynamic Link`.link_name = %(supplier)s
+ and `tabDynamic Link`.link_doctype = 'Supplier'
+ and `tabContact`.name like %(txt)s
+ """, {
+ 'supplier': supplier,
+ 'txt': '%%%s%%' % txt
+ })
diff --git a/erpnext/buying/doctype/supplier/test_supplier.js b/erpnext/buying/doctype/supplier/test_supplier.js
index bf7c192c91c..eaa4d0989d2 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.js
+++ b/erpnext/buying/doctype/supplier/test_supplier.js
@@ -74,4 +74,4 @@ QUnit.test("test: supplier", function(assert) {
},
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py
index 3a2e5d6dcef..4473ddea28e 100644
--- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py
+++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py
@@ -15,4 +15,4 @@ class SupplierItemGroup(Document):
'item_group': self.item_group
})
if exists:
- frappe.throw(_("Item Group has already been linked to this supplier."))
\ No newline at end of file
+ frappe.throw(_("Item Group has already been linked to this supplier."))
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index 6a4c02c075c..25e4e2a4dcf 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -166,4 +166,4 @@ def set_expired_status():
`tabSupplier Quotation` SET `status` = 'Expired'
WHERE
`status` not in ('Cancelled', 'Stopped') AND `valid_till` < %s
- """, (nowdate()))
\ No newline at end of file
+ """, (nowdate()))
diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js
index 2d2b29cb916..20fb43026ab 100644
--- a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js
+++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js
@@ -71,4 +71,4 @@ QUnit.test("test: supplier quotation", function(assert) {
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js
index b151824ba68..0a51565b08e 100644
--- a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js
+++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js
@@ -31,4 +31,4 @@ QUnit.test("test: supplier quotation with item wise discount", function(assert){
() => frappe.timeout(0.3),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js
index e37731eb579..7ea3e6079cd 100644
--- a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js
+++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js
@@ -34,4 +34,4 @@ QUnit.test("test: supplier quotation with taxes and charges", function(assert) {
() => frappe.timeout(0.3),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js
index 5f5f54b79f5..b4cd852c32f 100644
--- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js
+++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js
@@ -93,5 +93,3 @@ var loadAllStandings = function(frm) {
}
});
};
-
-
diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py
index 3d2305e2853..8e5cce5696b 100644
--- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_dashboard.py
@@ -13,4 +13,4 @@ def get_data():
'items': ['Supplier Scorecard Period']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
index 25282405492..a5f05ea5258 100644
--- a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
@@ -128,4 +128,3 @@ valid_scorecard = [
"weighting_function":"{total_score} * max( 0, min ( 1 , (12 - {period_number}) / 12) )"
}
]
-
diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py b/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py
index 4eef4b4e03e..3babfc8cab3 100644
--- a/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py
+++ b/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py
@@ -72,4 +72,4 @@ test_bad_criteria = [
"criteria_name":"Fake Criteria 3",
"max_score":100.0
},
-]
\ No newline at end of file
+]
diff --git a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py
index 9938710e6e6..cc345e96bb8 100644
--- a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py
+++ b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.py
@@ -109,4 +109,3 @@ def make_supplier_scorecard(source_name, target_doc=None):
}, target_doc, post_process, ignore_permissions=True)
return doc
-
diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py
index 1ba5d06c536..678855a457b 100644
--- a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py
+++ b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.py
@@ -26,4 +26,4 @@ def get_standings_list():
`tabSupplier Scorecard Standing` scs""",
{}, as_dict=1)
- return standings
\ No newline at end of file
+ return standings
diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
index 37fdc5724f5..89a6459bbab 100644
--- a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
+++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
@@ -493,4 +493,4 @@ def get_rfq_response_days(scorecard):
total_sq_days = 0
- return total_sq_days
\ No newline at end of file
+ return total_sq_days
diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py
index fe6dde50489..14b87105e66 100644
--- a/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py
+++ b/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py
@@ -54,4 +54,4 @@ test_bad_variables = [
"variable_label":"Fake Variable 1",
"path":"get_fake_variable1"
},
-]
\ No newline at end of file
+]
diff --git a/erpnext/buying/print_format/drop_shipping_format/drop_shipping_format.json b/erpnext/buying/print_format/drop_shipping_format/drop_shipping_format.json
index 0c7cad6e942..2e2a04826a3 100644
--- a/erpnext/buying/print_format/drop_shipping_format/drop_shipping_format.json
+++ b/erpnext/buying/print_format/drop_shipping_format/drop_shipping_format.json
@@ -13,6 +13,6 @@
"name": "Drop Shipping Format",
"owner": "Administrator",
"print_format_builder": 0,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
index beeca091c8a..99bcbe633cc 100644
--- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
@@ -296,4 +296,4 @@ def get_po_entries(conditions):
{conditions}
GROUP BY
parent.name, child.item_code
- """.format(conditions=conditions), as_dict=1) #nosec
\ No newline at end of file
+ """.format(conditions=conditions), as_dict=1) #nosec
diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
index 44ab767c0a9..c36083f2aff 100644
--- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
@@ -68,4 +68,4 @@ class TestProcurementTracker(unittest.TestCase):
"actual_delivery_date": date_obj
}
- return expected_data
\ No newline at end of file
+ return expected_data
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
index 89be62231b9..bda172769a9 100644
--- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
@@ -268,4 +268,3 @@ def get_columns(filters):
])
return columns
-
diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js
index 83d25d80ba2..90919dcc6a3 100644
--- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js
+++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.js
@@ -5,4 +5,4 @@ frappe.require("assets/erpnext/js/purchase_trends_filters.js", function() {
frappe.query_reports["Purchase Order Trends"] = {
filters: erpnext.get_purchase_trends_filters()
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
index 1ed6cad6b46..095a44319d6 100644
--- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
+++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
@@ -55,4 +55,4 @@ def get_chart_data(data, conditions, filters):
"lineOptions": {
"regionFill": 1
}
- }
\ No newline at end of file
+ }
diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py
index 0c0d4f0531d..9a45972837b 100644
--- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py
+++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py
@@ -149,4 +149,4 @@ def get_columns():
"fieldtype": "Float",
"width": 110
}
- ]
\ No newline at end of file
+ ]
diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
index d8de701bf6e..cb304a1fdab 100644
--- a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
+++ b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
@@ -33,4 +33,4 @@ def make_purchase_receipt_against_po(po, quantity=5):
pr.items[0].qty = quantity
pr.supplier_warehouse = '_Test Warehouse 1 - _TC'
pr.insert()
- pr.submit()
\ No newline at end of file
+ pr.submit()
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
index 68426abbb04..96cacb6f1b5 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
@@ -94,4 +94,4 @@ def get_po_items_to_supply(filters):
["Purchase Order", "transaction_date", ">=", filters.from_date],
["Purchase Order", "docstatus", "=", 1]
]
- )
\ No newline at end of file
+ )
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html
index 098214d741c..015b31c2064 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html
@@ -129,4 +129,4 @@
-
Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}
\ No newline at end of file
+
Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
index 80e521a8bfa..7a8d08dd22d 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
@@ -174,4 +174,4 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
});
dialog.show();
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
index 2b371915f32..a5a3105a847 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py
@@ -263,4 +263,4 @@ def get_message():
Expires today / Already Expired
- """
\ No newline at end of file
+ """
diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py
index a73cb0d62ec..17928634e78 100644
--- a/erpnext/buying/utils.py
+++ b/erpnext/buying/utils.py
@@ -102,4 +102,3 @@ def get_linked_material_requests(items):
mr_list.append(material_request)
return mr_list
-
diff --git a/erpnext/change_log/v13/v13_10_0.md b/erpnext/change_log/v13/v13_10_0.md
new file mode 100644
index 00000000000..ee844e5526a
--- /dev/null
+++ b/erpnext/change_log/v13/v13_10_0.md
@@ -0,0 +1,58 @@
+# Version 13.10.0 Release Notes
+
+### Features & Enhancements
+- POS invoice coupon code feature ([#27004](https://github.com/frappe/erpnext/pull/27004))
+- Add Primary Address and Contact section in Supplier ([#27197](https://github.com/frappe/erpnext/pull/27197))
+- Capacity for Service Unit, concurrent appointments based on Capacity, Patient enhancements ([#24860](https://github.com/frappe/erpnext/pull/24860))
+- Increase number of supported currency exchanges ([#26763](https://github.com/frappe/erpnext/pull/26763))
+- South Africa VAT Audit Report ([#27017](https://github.com/frappe/erpnext/pull/27017))
+- Training Event Status Update and Validations ([#26698](https://github.com/frappe/erpnext/pull/26698))
+- Allow draft POS Invoices even if no stock available ([#27106](https://github.com/frappe/erpnext/pull/27106))
+- Column for total amount due in Accounts Receivable/Payable Summary ([#27069](https://github.com/frappe/erpnext/pull/27069))
+- Provision to create customer from opportunity ([#27141](https://github.com/frappe/erpnext/pull/27141))
+- Employee reminders ([#25735](https://github.com/frappe/erpnext/pull/25735))
+- Fetching details from supplier/customer groups ([#26131](https://github.com/frappe/erpnext/pull/26131))
+- Unreconcile on cancellation of bank transaction ([#27109](https://github.com/frappe/erpnext/pull/27109))
+
+### Fixes
+- Healthcare Redesign Changes ([#27100](https://github.com/frappe/erpnext/pull/27100))
+- Eway bill version changed to 1.0.0421 ([#27044](https://github.com/frappe/erpnext/pull/27044))
+- Org Chart fixes ([#26952](https://github.com/frappe/erpnext/pull/26952))
+- TDS calculation on net total ([#27058](https://github.com/frappe/erpnext/pull/27058))
+- Dimension filter query fix to avoid including disabled dimensions ([#26988](https://github.com/frappe/erpnext/pull/26988))
+- Various minor perf fixes for ledger postings ([#26775](https://github.com/frappe/erpnext/pull/26775))
+- Healthcare Service Unit fixes ([#27273](https://github.com/frappe/erpnext/pull/27273))
+- Selected batch no changed on changing of qty ([#27126](https://github.com/frappe/erpnext/pull/27126))
+- Changed label to "Inpatient Visit Charge" in appointment type ([#26906](https://github.com/frappe/erpnext/pull/26906))
+- Stock Analytics Report must consider warehouse during calculation ([#26908](https://github.com/frappe/erpnext/pull/26908))
+- Reduce Sales Invoice row size ([#27136](https://github.com/frappe/erpnext/pull/27136))
+- Allow backdated discharge for inpatient ([#25124](https://github.com/frappe/erpnext/pull/25124))
+- Sequence of sub-operations in job card ([#27138](https://github.com/frappe/erpnext/pull/27138))
+- Social media post fixes ([#24664](https://github.com/frappe/erpnext/pull/24664))
+- Consolidated balance sheet showing incorrect values ([#26975](https://github.com/frappe/erpnext/pull/26975))
+- Correct company address not getting copied from Purchase Order to Invoice ([#27217](https://github.com/frappe/erpnext/pull/27217))
+- Add child item groups into the filters ([#26997](https://github.com/frappe/erpnext/pull/26997))
+- Pass planned start date to in work order from production plan ([#27031](https://github.com/frappe/erpnext/pull/27031))
+- Filtering of items in Sales and Purchase Orders ([#26936](https://github.com/frappe/erpnext/pull/26936))
+- Sales order qty update fails in "Update Items" button ([#26992](https://github.com/frappe/erpnext/pull/26992))
+- Refactor stock module onboarding ([#25745](https://github.com/frappe/erpnext/pull/25745))
+- Calculation of gross profit percentage in Gross Profit Report ([#27045](https://github.com/frappe/erpnext/pull/27045))
+- Correct price list rate field value in return Sales Invoice ([#27105](https://github.com/frappe/erpnext/pull/27105))
+- Return Qty in PR/DN for legacy data ([#27003](https://github.com/frappe/erpnext/pull/27003))
+- Sales pipeline graph issue ([#26626](https://github.com/frappe/erpnext/pull/26626))
+- Additional salary processing ([#27005](https://github.com/frappe/erpnext/pull/27005))
+- Dimension filter query fix to avoid including disabled dimensions ([#27006](https://github.com/frappe/erpnext/pull/27006))
+- Incorrect Gl Entry on period closing involving finance books ([#27104](https://github.com/frappe/erpnext/pull/26921))
+- Set production plan to completed even on over production ([#27032](https://github.com/frappe/erpnext/pull/27032))
+- Budget variance missing values ([#26963](https://github.com/frappe/erpnext/pull/26963))
+- No able to create asset depreciation entry when cost_center is mandatory ([#26912](https://github.com/frappe/erpnext/pull/26912))
+- Keep stock entry title & purpose in sync ([#27043](https://github.com/frappe/erpnext/pull/27043))
+- Add mandatory depends on condition for export type field ([#26957](https://github.com/frappe/erpnext/pull/26957))
+- Fixed patched which were breaking while migrating ([#27205](https://github.com/frappe/erpnext/pull/27205))
+- ZeroDivisionError on creating e-invoice for credit note ([#26919](https://github.com/frappe/erpnext/pull/26919))
+- Stock analytics report date range issues and add company filter ([#27014](https://github.com/frappe/erpnext/pull/27014))
+- Stock Ledger report not working if include uom selected in filter ([#27127](https://github.com/frappe/erpnext/pull/27127))
+- Show proper currency symbol in Taxes and Charges table ([#26935](https://github.com/frappe/erpnext/pull/26935))
+- Operation time auto set to zero ([#27190](https://github.com/frappe/erpnext/pull/27190))
+- Set account for change amount even if pos profile not found ([#26986](https://github.com/frappe/erpnext/pull/26986))
+- Discard empty rows from update items ([#27021](https://github.com/frappe/erpnext/pull/27021))
\ No newline at end of file
diff --git a/erpnext/commands/__init__.py b/erpnext/commands/__init__.py
index a991cf9881e..2276c738fbe 100644
--- a/erpnext/commands/__init__.py
+++ b/erpnext/commands/__init__.py
@@ -46,4 +46,4 @@ def make_demo(context, site, domain='Manufacturing', days=100,
commands = [
make_demo
-]
\ No newline at end of file
+]
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 73d2411edef..e02e7351520 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -14,7 +14,7 @@ from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_a
from erpnext.utilities.transaction_base import TransactionBase
from erpnext.buying.utils import update_last_purchase_rate
from erpnext.controllers.sales_and_purchase_return import validate_return
-from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
+from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled, get_party_account
from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction,
apply_pricing_rule_for_free_items, get_applied_pricing_rules)
from erpnext.exceptions import InvalidCurrency
@@ -164,7 +164,8 @@ class AccountsController(TransactionBase):
self.set_due_date()
self.set_payment_schedule()
self.validate_payment_schedule_amount()
- self.validate_due_date()
+ if not self.get('ignore_default_payment_terms_template'):
+ self.validate_due_date()
self.validate_advance_entries()
def validate_non_invoice_documents_schedule(self):
@@ -842,7 +843,7 @@ class AccountsController(TransactionBase):
dr_or_cr = "credit"
rev_dr_cr = "debit"
supplier_or_customer = self.supplier
-
+
else:
dr_or_cr = "debit"
rev_dr_cr = "credit"
@@ -853,11 +854,11 @@ class AccountsController(TransactionBase):
discount_amount = item.discount_amount * item.qty
if self.doctype == "Purchase Invoice":
income_or_expense_account = (item.expense_account
- if (not item.enable_deferred_expense or self.is_return)
+ if (not item.enable_deferred_expense or self.is_return)
else item.deferred_expense_account)
else:
income_or_expense_account = (item.income_account
- if (not item.enable_deferred_revenue or self.is_return)
+ if (not item.enable_deferred_revenue or self.is_return)
else item.deferred_revenue_account)
account_currency = get_account_currency(item.discount_account)
@@ -866,7 +867,7 @@ class AccountsController(TransactionBase):
"account": item.discount_account,
"against": supplier_or_customer,
dr_or_cr: flt(discount_amount, item.precision('discount_amount')),
- dr_or_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
+ dr_or_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
item.precision('discount_amount')),
"cost_center": item.cost_center,
"project": item.project
@@ -879,7 +880,7 @@ class AccountsController(TransactionBase):
"account": income_or_expense_account,
"against": supplier_or_customer,
rev_dr_cr: flt(discount_amount, item.precision('discount_amount')),
- rev_dr_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
+ rev_dr_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
item.precision('discount_amount')),
"cost_center": item.cost_center,
"project": item.project or self.project
@@ -894,8 +895,8 @@ class AccountsController(TransactionBase):
dr_or_cr: self.discount_amount,
"cost_center": self.cost_center
}, item=self)
- )
-
+ )
+
def allocate_advance_taxes(self, gl_entries):
tax_map = self.get_tax_map()
for pe in self.get("advances"):
@@ -1223,7 +1224,7 @@ class AccountsController(TransactionBase):
po_or_so = self.get('items')[0].get('purchase_order')
po_or_so_doctype = "Purchase Order"
po_or_so_doctype_name = "purchase_order"
-
+
return po_or_so, po_or_so_doctype, po_or_so_doctype_name
def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
@@ -1232,14 +1233,14 @@ class AccountsController(TransactionBase):
return True
elif self.linked_order_has_payment_schedule(po_or_so):
return True
-
+
return False
def all_items_have_same_po_or_so(self, po_or_so, fieldname):
for item in self.get('items'):
if item.get(fieldname) != po_or_so:
return False
-
+
return True
def linked_order_has_payment_terms_template(self, po_or_so, doctype):
@@ -1367,6 +1368,67 @@ class AccountsController(TransactionBase):
return False
+ def process_common_party_accounting(self):
+ is_invoice = self.doctype in ['Sales Invoice', 'Purchase Invoice']
+ if not is_invoice:
+ return
+
+ if frappe.db.get_single_value('Accounts Settings', 'enable_common_party_accounting'):
+ party_link = self.get_common_party_link()
+ if party_link and self.outstanding_amount:
+ self.create_advance_and_reconcile(party_link)
+
+ def get_common_party_link(self):
+ party_type, party = self.get_party()
+ return frappe.db.get_value(
+ doctype='Party Link',
+ filters={'secondary_role': party_type, 'secondary_party': party},
+ fieldname=['primary_role', 'primary_party'],
+ as_dict=True
+ )
+
+ def create_advance_and_reconcile(self, party_link):
+ secondary_party_type, secondary_party = self.get_party()
+ primary_party_type, primary_party = party_link.primary_role, party_link.primary_party
+
+ primary_account = get_party_account(primary_party_type, primary_party, self.company)
+ secondary_account = get_party_account(secondary_party_type, secondary_party, self.company)
+
+ jv = frappe.new_doc('Journal Entry')
+ jv.voucher_type = 'Journal Entry'
+ jv.posting_date = self.posting_date
+ jv.company = self.company
+ jv.remark = 'Adjustment for {} {}'.format(self.doctype, self.name)
+
+ reconcilation_entry = frappe._dict()
+ advance_entry = frappe._dict()
+
+ reconcilation_entry.account = secondary_account
+ reconcilation_entry.party_type = secondary_party_type
+ reconcilation_entry.party = secondary_party
+ reconcilation_entry.reference_type = self.doctype
+ reconcilation_entry.reference_name = self.name
+ reconcilation_entry.cost_center = self.cost_center
+
+ advance_entry.account = primary_account
+ advance_entry.party_type = primary_party_type
+ advance_entry.party = primary_party
+ advance_entry.cost_center = self.cost_center
+ advance_entry.is_advance = 'Yes'
+
+ if self.doctype == 'Sales Invoice':
+ reconcilation_entry.credit_in_account_currency = self.outstanding_amount
+ advance_entry.debit_in_account_currency = self.outstanding_amount
+ else:
+ advance_entry.credit_in_account_currency = self.outstanding_amount
+ reconcilation_entry.debit_in_account_currency = self.outstanding_amount
+
+ jv.append('accounts', reconcilation_entry)
+ jv.append('accounts', advance_entry)
+
+ jv.save()
+ jv.submit()
+
@frappe.whitelist()
def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
@@ -1841,6 +1903,11 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
for d in data:
new_child_flag = False
+
+ if not d.get("item_code"):
+ # ignore empty rows
+ continue
+
if not d.get("docname"):
new_child_flag = True
check_doc_permissions(parent, 'create')
@@ -1863,7 +1930,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
qty_unchanged = prev_qty == new_qty
uom_unchanged = prev_uom == new_uom
conversion_factor_unchanged = prev_con_fac == new_con_fac
- date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
+ date_unchanged = prev_date == getdate(new_date) if prev_date and new_date else False # in case of delivery note etc
if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged:
continue
@@ -1978,4 +2045,4 @@ def validate_regional(doc):
@erpnext.allow_regional
def validate_einvoice_fields(doc):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 051481ff603..8c361a2e561 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -344,4 +344,3 @@ def create_variant_doc_for_quick_entry(template, args):
variant.name = variant.item_code
validate_item_variant_attributes(variant, args)
return variant.as_dict()
-
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 21c052a3912..4b4c8befa53 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -526,6 +526,9 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
if meta.is_tree:
query_filters.append(['is_group', '=', 0])
+ if meta.has_field('disabled'):
+ query_filters.append(['disabled', '!=', 1])
+
if meta.has_field('company'):
query_filters.append(['company', '=', filters.get('company')])
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 80ccc6d75b2..01486fcd65d 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -329,7 +329,6 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.po_detail = source_doc.po_detail
target_doc.pr_detail = source_doc.pr_detail
target_doc.purchase_invoice_item = source_doc.name
- target_doc.price_list_rate = 0
elif doctype == "Delivery Note":
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
@@ -360,7 +359,6 @@ def make_return_doc(doctype, source_name, target_doc=None):
else:
target_doc.pos_invoice_item = source_doc.name
- target_doc.price_list_rate = 0
if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return
@@ -396,19 +394,6 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None
if not return_against:
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
- if not return_against and voucher_type == 'Sales Invoice' and sle:
- return get_incoming_rate({
- "item_code": sle.item_code,
- "warehouse": sle.warehouse,
- "posting_date": sle.get('posting_date'),
- "posting_time": sle.get('posting_time'),
- "qty": sle.actual_qty,
- "serial_no": sle.get('serial_no'),
- "company": sle.company,
- "voucher_type": sle.voucher_type,
- "voucher_no": sle.voucher_no
- }, raise_error_if_no_rate=False)
-
return_against_item_field = get_return_against_item_fields(voucher_type)
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
@@ -419,7 +404,24 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None
else:
select_field = "abs(stock_value_difference / actual_qty)"
- return flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+ rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+ if not (rate and return_against) and voucher_type in ['Sales Invoice', 'Delivery Note']:
+ rate = frappe.db.get_value(f'{voucher_type} Item', voucher_detail_no, 'incoming_rate')
+
+ if not rate and sle:
+ rate = get_incoming_rate({
+ "item_code": sle.item_code,
+ "warehouse": sle.warehouse,
+ "posting_date": sle.get('posting_date'),
+ "posting_time": sle.get('posting_time'),
+ "qty": sle.actual_qty,
+ "serial_no": sle.get('serial_no'),
+ "company": sle.company,
+ "voucher_type": sle.voucher_type,
+ "voucher_no": sle.voucher_no
+ }, raise_error_if_no_rate=False)
+
+ return rate
def get_return_against_item_fields(voucher_type):
return_against_item_fields = {
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index da2765deded..844c40c8a64 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import cint, flt, cstr, get_link_to_form, nowtime
-from frappe import _, throw
+from frappe import _, bold, throw
from erpnext.stock.get_item_details import get_bin_details
from erpnext.stock.utils import get_incoming_rate
from erpnext.stock.get_item_details import get_conversion_factor
@@ -16,7 +16,6 @@ from erpnext.controllers.stock_controller import StockController
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
class SellingController(StockController):
-
def get_feed(self):
return _("To {0} | {1} {2}").format(self.customer_name, self.currency,
self.grand_total)
@@ -169,39 +168,96 @@ class SellingController(StockController):
def validate_selling_price(self):
def throw_message(idx, item_name, rate, ref_rate_field):
- bold_net_rate = frappe.bold("net rate")
- msg = (_("""Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {}""")
- .format(idx, frappe.bold(item_name), frappe.bold(ref_rate_field), bold_net_rate, frappe.bold(rate)))
- msg += "
"
- msg += (_("""You can alternatively disable selling price validation in {} to bypass this validation.""")
- .format(get_link_to_form("Selling Settings", "Selling Settings")))
- frappe.throw(msg, title=_("Invalid Selling Price"))
+ throw(_("""Row #{0}: Selling rate for item {1} is lower than its {2}.
+ Selling {3} should be atleast {4}.
Alternatively,
+ you can disable selling price validation in {5} to bypass
+ this validation.""").format(
+ idx,
+ bold(item_name),
+ bold(ref_rate_field),
+ bold("net rate"),
+ bold(rate),
+ get_link_to_form("Selling Settings", "Selling Settings"),
+ ), title=_("Invalid Selling Price"))
- if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
- return
- if hasattr(self, "is_return") and self.is_return:
+ if (
+ self.get("is_return")
+ or not frappe.db.get_single_value("Selling Settings", "validate_selling_price")
+ ):
return
- for it in self.get("items"):
- if not it.item_code:
+ is_internal_customer = self.get('is_internal_customer')
+ valuation_rate_map = {}
+
+ for item in self.items:
+ if not item.item_code:
continue
- last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
- last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1)
- if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
- throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate")
+ last_purchase_rate, is_stock_item = frappe.get_cached_value(
+ "Item", item.item_code, ("last_purchase_rate", "is_stock_item")
+ )
- last_valuation_rate = frappe.db.sql("""
- SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s
- AND warehouse = %s AND valuation_rate > 0
- ORDER BY posting_date DESC, posting_time DESC, creation DESC LIMIT 1
- """, (it.item_code, it.warehouse))
- if last_valuation_rate:
- last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] * (it.conversion_factor or 1)
- if is_stock_item and flt(it.base_net_rate) < flt(last_valuation_rate_in_sales_uom) \
- and not self.get('is_internal_customer'):
- throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate")
+ last_purchase_rate_in_sales_uom = (
+ last_purchase_rate * (item.conversion_factor or 1)
+ )
+ if flt(item.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
+ throw_message(
+ item.idx,
+ item.item_name,
+ last_purchase_rate_in_sales_uom,
+ "last purchase rate"
+ )
+
+ if is_internal_customer or not is_stock_item:
+ continue
+
+ valuation_rate_map[(item.item_code, item.warehouse)] = None
+
+ if not valuation_rate_map:
+ return
+
+ or_conditions = (
+ f"""(item_code = {frappe.db.escape(valuation_rate[0])}
+ and warehouse = {frappe.db.escape(valuation_rate[1])})"""
+ for valuation_rate in valuation_rate_map
+ )
+
+ valuation_rates = frappe.db.sql(f"""
+ select
+ item_code, warehouse, valuation_rate
+ from
+ `tabBin`
+ where
+ ({" or ".join(or_conditions)})
+ and valuation_rate > 0
+ """, as_dict=True)
+
+ for rate in valuation_rates:
+ valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate
+
+ for item in self.items:
+ if not item.item_code:
+ continue
+
+ last_valuation_rate = valuation_rate_map.get(
+ (item.item_code, item.warehouse)
+ )
+
+ if not last_valuation_rate:
+ continue
+
+ last_valuation_rate_in_sales_uom = (
+ last_valuation_rate * (item.conversion_factor or 1)
+ )
+
+ if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
+ throw_message(
+ item.idx,
+ item.item_name,
+ last_valuation_rate_in_sales_uom,
+ "valuation rate"
+ )
def get_item_list(self):
il = []
@@ -306,7 +362,7 @@ class SellingController(StockController):
sales_order.update_reserved_qty(so_item_rows)
def set_incoming_rate(self):
- if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"):
+ if self.doctype not in ("Delivery Note", "Sales Invoice"):
return
items = self.get("items") + (self.get("packed_items") or [])
@@ -315,18 +371,19 @@ class SellingController(StockController):
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get('stock_qty') or d.get('actual_qty'))
- d.incoming_rate = get_incoming_rate({
- "item_code": d.item_code,
- "warehouse": d.warehouse,
- "posting_date": self.get('posting_date') or self.get('transaction_date'),
- "posting_time": self.get('posting_time') or nowtime(),
- "qty": qty if cint(self.get("is_return")) else (-1 * qty),
- "serial_no": d.get('serial_no'),
- "company": self.company,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "allow_zero_valuation": d.get("allow_zero_valuation")
- }, raise_error_if_no_rate=False)
+ if not d.incoming_rate:
+ d.incoming_rate = get_incoming_rate({
+ "item_code": d.item_code,
+ "warehouse": d.warehouse,
+ "posting_date": self.get('posting_date') or self.get('transaction_date'),
+ "posting_time": self.get('posting_time') or nowtime(),
+ "qty": qty if cint(self.get("is_return")) else (-1 * qty),
+ "serial_no": d.get('serial_no'),
+ "company": self.company,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "allow_zero_valuation": d.get("allow_zero_valuation")
+ }, raise_error_if_no_rate=False)
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
@@ -366,7 +423,7 @@ class SellingController(StockController):
or (cint(self.is_return) and self.docstatus==2)):
sl_entries.append(self.get_sle_for_source_warehouse(d))
- if d.target_warehouse:
+ if d.target_warehouse and self.get("is_internal_customer"):
sl_entries.append(self.get_sle_for_target_warehouse(d))
if d.warehouse and ((not cint(self.is_return) and self.docstatus==2)
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 943f7aaeb12..7b24e50b143 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -3,7 +3,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import flt, comma_or, nowdate, getdate
+from frappe.utils import flt, comma_or, nowdate, getdate, now
from frappe import _
from frappe.model.document import Document
@@ -86,7 +86,8 @@ status_map = {
],
"Bank Transaction": [
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],
- ["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"]
+ ["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"],
+ ["Cancelled", "eval:self.docstatus == 2"]
],
"POS Opening Entry": [
["Draft", None],
@@ -336,10 +337,14 @@ class StatusUpdater(Document):
target.notify_update()
def _update_modified(self, args, update_modified):
- args['update_modified'] = ''
- if update_modified:
- args['update_modified'] = ', modified = now(), modified_by = {0}'\
- .format(frappe.db.escape(frappe.session.user))
+ if not update_modified:
+ args['update_modified'] = ''
+ return
+
+ args['update_modified'] = ', modified = {0}, modified_by = {1}'.format(
+ frappe.db.escape(now()),
+ frappe.db.escape(frappe.session.user)
+ )
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
ref_fieldname = frappe.scrub(ref_dt)
diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py
index 36ae1102164..969829f9651 100644
--- a/erpnext/controllers/subcontracting.py
+++ b/erpnext/controllers/subcontracting.py
@@ -390,4 +390,4 @@ class Subcontracting():
incorrect_sn = "\n".join(incorrect_sn)
link = get_link_to_form('Purchase Order', row.purchase_order)
msg = f'The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}'
- frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
\ No newline at end of file
+ frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index df73f09c493..f7c6b6c7993 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -235,4 +235,3 @@ def _get_employee_from_user(user):
# frappe.db.exists returns a tuple of a tuple
return frappe.get_doc('Employee', employee_docname[0][0])
return None
-
diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
index dc3ae8bf41a..0c64eb8e822 100644
--- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
+++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
@@ -7,4 +7,4 @@ function check_times(frm) {
frappe.throw(__('In row {0} of Appointment Booking Slots: "To Time" must be later than "From Time".', [i + 1]));
}
});
-}
\ No newline at end of file
+}
diff --git a/erpnext/crm/doctype/contract/contract.js b/erpnext/crm/doctype/contract/contract.js
index 99688551630..7848de7a727 100644
--- a/erpnext/crm/doctype/contract/contract.js
+++ b/erpnext/crm/doctype/contract/contract.js
@@ -15,7 +15,7 @@ frappe.ui.form.on("Contract", {
let contract_template = r.message.contract_template;
frm.set_value("contract_terms", r.message.contract_terms);
frm.set_value("requires_fulfilment", contract_template.requires_fulfilment);
-
+
if (frm.doc.requires_fulfilment) {
// Populate the fulfilment terms table from a contract template, if any
r.message.contract_template.fulfilment_terms.forEach(element => {
@@ -23,7 +23,7 @@ frappe.ui.form.on("Contract", {
d.requirement = element.requirement;
});
frm.refresh_field("fulfilment_terms");
- }
+ }
}
}
});
diff --git a/erpnext/crm/doctype/contract/contract_list.js b/erpnext/crm/doctype/contract/contract_list.js
index 26a2907c7cc..7d5609651a1 100644
--- a/erpnext/crm/doctype/contract/contract_list.js
+++ b/erpnext/crm/doctype/contract/contract_list.js
@@ -9,4 +9,4 @@ frappe.listview_settings['Contract'] = {
return [__(doc.status), "gray", "status,=," + doc.status];
}
},
-};
\ No newline at end of file
+};
diff --git a/erpnext/crm/doctype/contract_template/contract_template.py b/erpnext/crm/doctype/contract_template/contract_template.py
index 69fd86f7fb5..9281220eef4 100644
--- a/erpnext/crm/doctype/contract_template/contract_template.py
+++ b/erpnext/crm/doctype/contract_template/contract_template.py
@@ -24,8 +24,8 @@ def get_contract_template(template_name, doc):
if contract_template.contract_terms:
contract_terms = frappe.render_template(contract_template.contract_terms, doc)
-
+
return {
- 'contract_template': contract_template,
+ 'contract_template': contract_template,
'contract_terms': contract_terms
- }
\ No newline at end of file
+ }
diff --git a/erpnext/crm/doctype/lead/lead_dashboard.py b/erpnext/crm/doctype/lead/lead_dashboard.py
index 69d8ca70926..3950d063f22 100644
--- a/erpnext/crm/doctype/lead/lead_dashboard.py
+++ b/erpnext/crm/doctype/lead/lead_dashboard.py
@@ -16,4 +16,4 @@ def get_data():
'items': ['Opportunity', 'Quotation']
},
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
index 263005ef6c5..7aa0b777596 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
@@ -2,8 +2,8 @@
// For license information, please see license.txt
frappe.ui.form.on('LinkedIn Settings', {
- onload: function(frm){
- if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){
+ onload: function(frm) {
+ if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret) {
frappe.confirm(
__('Session not valid, Do you want to login?'),
function(){
@@ -14,8 +14,9 @@ frappe.ui.form.on('LinkedIn Settings', {
}
);
}
+ frm.dashboard.set_headline(__("For more information, {0}.", [`${__('Click here')}`]));
},
- refresh: function(frm){
+ refresh: function(frm) {
if (frm.doc.session_status=="Expired"){
let msg = __("Session Not Active. Save doc to login.");
frm.dashboard.set_headline_alert(
@@ -53,7 +54,7 @@ frappe.ui.form.on('LinkedIn Settings', {
);
}
},
- login: function(frm){
+ login: function(frm) {
if (frm.doc.consumer_key && frm.doc.consumer_secret){
frappe.dom.freeze();
frappe.call({
@@ -67,7 +68,7 @@ frappe.ui.form.on('LinkedIn Settings', {
});
}
},
- after_save: function(frm){
+ after_save: function(frm) {
frm.trigger("login");
}
});
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json
index 9eacb0011c5..f882e36c32a 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json
@@ -2,6 +2,7 @@
"actions": [],
"creation": "2020-01-30 13:36:39.492931",
"doctype": "DocType",
+ "documentation": "https://docs.erpnext.com/docs/user/manual/en/CRM/linkedin-settings",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -87,7 +88,7 @@
],
"issingle": 1,
"links": [],
- "modified": "2020-04-16 23:22:51.966397",
+ "modified": "2021-02-18 15:19:21.920725",
"modified_by": "Administrator",
"module": "CRM",
"name": "LinkedIn Settings",
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index d8c6fb4f90f..9b88d78c1ff 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -3,11 +3,12 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe, requests, json
+import frappe
+import requests
from frappe import _
-from frappe.utils import get_site_url, get_url_to_form, get_link_to_form
+from frappe.utils import get_url_to_form
from frappe.model.document import Document
-from frappe.utils.file_manager import get_file, get_file_path
+from frappe.utils.file_manager import get_file_path
from six.moves.urllib.parse import urlencode
class LinkedInSettings(Document):
@@ -42,11 +43,7 @@ class LinkedInSettings(Document):
self.db_set("access_token", response["access_token"])
def get_member_profile(self):
- headers = {
- "Authorization": "Bearer {}".format(self.access_token)
- }
- url = "https://api.linkedin.com/v2/me"
- response = requests.get(url=url, headers=headers)
+ response = requests.get(url="https://api.linkedin.com/v2/me", headers=self.get_headers())
response = frappe.parse_json(response.content.decode())
frappe.db.set_value(self.doctype, self.name, {
@@ -55,16 +52,16 @@ class LinkedInSettings(Document):
"session_status": "Active"
})
frappe.local.response["type"] = "redirect"
- frappe.local.response["location"] = get_url_to_form("LinkedIn Settings","LinkedIn Settings")
+ frappe.local.response["location"] = get_url_to_form("LinkedIn Settings", "LinkedIn Settings")
- def post(self, text, media=None):
+ def post(self, text, title, media=None):
if not media:
- return self.post_text(text)
+ return self.post_text(text, title)
else:
media_id = self.upload_image(media)
if media_id:
- return self.post_text(text, media_id=media_id)
+ return self.post_text(text, title, media_id=media_id)
else:
frappe.log_error("Failed to upload media.","LinkedIn Upload Error")
@@ -82,9 +79,7 @@ class LinkedInSettings(Document):
}]
}
}
- headers = {
- "Authorization": "Bearer {}".format(self.access_token)
- }
+ headers = self.get_headers()
response = self.http_post(url=register_url, body=body, headers=headers)
if response.status_code == 200:
@@ -100,24 +95,33 @@ class LinkedInSettings(Document):
return None
- def post_text(self, text, media_id=None):
+ def post_text(self, text, title, media_id=None):
url = "https://api.linkedin.com/v2/shares"
- headers = {
- "X-Restli-Protocol-Version": "2.0.0",
- "Authorization": "Bearer {}".format(self.access_token),
- "Content-Type": "application/json; charset=UTF-8"
- }
+ headers = self.get_headers()
+ headers["X-Restli-Protocol-Version"] = "2.0.0"
+ headers["Content-Type"] = "application/json; charset=UTF-8"
+
body = {
"distribution": {
"linkedInDistributionTarget": {}
},
"owner":"urn:li:organization:{0}".format(self.company_id),
- "subject": "Test Share Subject",
+ "subject": title,
"text": {
"text": text
}
}
+ reference_url = self.get_reference_url(text)
+ if reference_url:
+ body["content"] = {
+ "contentEntities": [
+ {
+ "entityLocation": reference_url
+ }
+ ]
+ }
+
if media_id:
body["content"]= {
"contentEntities": [{
@@ -141,20 +145,60 @@ class LinkedInSettings(Document):
raise
except Exception as e:
- content = json.loads(response.content)
-
- if response.status_code == 401:
- self.db_set("session_status", "Expired")
- frappe.db.commit()
- frappe.throw(content["message"], title="LinkedIn Error - Unauthorized")
- elif response.status_code == 403:
- frappe.msgprint(_("You Didn't have permission to access this API"))
- frappe.throw(content["message"], title="LinkedIn Error - Access Denied")
- else:
- frappe.throw(response.reason, title=response.status_code)
-
+ self.api_error(response)
+
return response
+ def get_headers(self):
+ return {
+ "Authorization": "Bearer {}".format(self.access_token)
+ }
+
+ def get_reference_url(self, text):
+ import re
+ regex_url = r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
+ urls = re.findall(regex_url, text)
+ if urls:
+ return urls[0]
+
+ def delete_post(self, post_id):
+ try:
+ response = requests.delete(url="https://api.linkedin.com/v2/shares/urn:li:share:{0}".format(post_id), headers=self.get_headers())
+ if response.status_code !=200:
+ raise
+ except Exception:
+ self.api_error(response)
+
+ def get_post(self, post_id):
+ url = "https://api.linkedin.com/v2/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:{0}&shares[0]=urn:li:share:{1}".format(self.company_id, post_id)
+
+ try:
+ response = requests.get(url=url, headers=self.get_headers())
+ if response.status_code !=200:
+ raise
+
+ except Exception:
+ self.api_error(response)
+
+ response = frappe.parse_json(response.content.decode())
+ if len(response.elements):
+ return response.elements[0]
+
+ return None
+
+ def api_error(self, response):
+ content = frappe.parse_json(response.content.decode())
+
+ if response.status_code == 401:
+ self.db_set("session_status", "Expired")
+ frappe.db.commit()
+ frappe.throw(content["message"], title=_("LinkedIn Error - Unauthorized"))
+ elif response.status_code == 403:
+ frappe.msgprint(_("You didn't have permission to access this API"))
+ frappe.throw(content["message"], title=_("LinkedIn Error - Access Denied"))
+ else:
+ frappe.throw(response.reason, title=response.status_code)
+
@frappe.whitelist(allow_guest=True)
def callback(code=None, error=None, error_description=None):
if not error:
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 089a63fc1cd..5cc63d4511b 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -57,7 +57,7 @@ frappe.ui.form.on("Opportunity", {
if (frm.doc.status == "Lost"){
frm.trigger('set_as_lost_dialog');
}
-
+
},
customer_address: function(frm, cdt, cdn) {
@@ -95,9 +95,17 @@ frappe.ui.form.on("Opportunity", {
}, __('Create'));
}
- frm.add_custom_button(__('Quotation'),
- cur_frm.cscript.create_quotation, __('Create'));
+ if (frm.doc.opportunity_from != "Customer") {
+ frm.add_custom_button(__('Customer'),
+ function() {
+ frm.trigger("make_customer")
+ }, __('Create'));
+ }
+ frm.add_custom_button(__('Quotation'),
+ function() {
+ frm.trigger("create_quotation")
+ }, __('Create'));
}
if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) {
@@ -194,6 +202,13 @@ erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({
method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation",
frm: cur_frm
})
+ },
+
+ make_customer: function() {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.crm.doctype.opportunity.opportunity.make_customer",
+ frm: cur_frm
+ })
}
});
@@ -215,4 +230,4 @@ cur_frm.cscript.item_code = function(doc, cdt, cdn) {
}
})
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 23ad98a2828..a74a94afd68 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -287,6 +287,24 @@ def make_request_for_quotation(source_name, target_doc=None):
return doclist
+@frappe.whitelist()
+def make_customer(source_name, target_doc=None):
+ def set_missing_values(source, target):
+ if source.opportunity_from == "Lead":
+ target.lead_name = source.party_name
+
+ doclist = get_mapped_doc("Opportunity", source_name, {
+ "Opportunity": {
+ "doctype": "Customer",
+ "field_map": {
+ "currency": "default_currency",
+ "customer_name": "customer_name"
+ }
+ }
+ }, target_doc, set_missing_values)
+
+ return doclist
+
@frappe.whitelist()
def make_supplier_quotation(source_name, target_doc=None):
doclist = get_mapped_doc("Opportunity", source_name, {
@@ -372,4 +390,4 @@ def get_events(start, end, filters=None):
"start": start,
"end": end
}, as_dict=True, update={"allDay": 0})
- return data
\ No newline at end of file
+ return data
diff --git a/erpnext/crm/doctype/opportunity/opportunity_dashboard.py b/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
index 68f0104fd6c..b8c53f077ae 100644
--- a/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
+++ b/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Quotation', 'Supplier Quotation']
},
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py
index 04cd8a26cad..52aa0b036ae 100644
--- a/erpnext/crm/doctype/opportunity/test_opportunity.py
+++ b/erpnext/crm/doctype/opportunity/test_opportunity.py
@@ -87,4 +87,4 @@ def make_opportunity(**args):
})
opp_doc.insert()
- return opp_doc
\ No newline at end of file
+ return opp_doc
diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js
index 0ce8b44e19b..a8f5deea535 100644
--- a/erpnext/crm/doctype/social_media_post/social_media_post.js
+++ b/erpnext/crm/doctype/social_media_post/social_media_post.js
@@ -1,67 +1,139 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Social Media Post', {
- validate: function(frm){
- if (frm.doc.twitter === 0 && frm.doc.linkedin === 0){
- frappe.throw(__("Select atleast one Social Media from Share on."))
- }
- if (frm.doc.scheduled_time) {
- let scheduled_time = new Date(frm.doc.scheduled_time);
- let date_time = new Date();
- if (scheduled_time.getTime() < date_time.getTime()){
- frappe.throw(__("Invalid Scheduled Time"));
- }
- }
- if (frm.doc.text?.length > 280){
- frappe.throw(__("Length Must be less than 280."))
- }
- },
- refresh: function(frm){
- if (frm.doc.docstatus === 1){
- if (frm.doc.post_status != "Posted"){
- add_post_btn(frm);
- }
- else if (frm.doc.post_status == "Posted"){
- frm.set_df_property('sheduled_time', 'read_only', 1);
- }
+ validate: function(frm) {
+ if (frm.doc.twitter === 0 && frm.doc.linkedin === 0) {
+ frappe.throw(__("Select atleast one Social Media Platform to Share on."));
+ }
+ if (frm.doc.scheduled_time) {
+ let scheduled_time = new Date(frm.doc.scheduled_time);
+ let date_time = new Date();
+ if (scheduled_time.getTime() < date_time.getTime()) {
+ frappe.throw(__("Scheduled Time must be a future time."));
+ }
+ }
+ frm.trigger('validate_tweet_length');
+ },
- let html='';
- if (frm.doc.twitter){
- let color = frm.doc.twitter_post_id ? "green" : "red";
- let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
- html += `
- Twitter : ${status}
-
` ;
- }
- if (frm.doc.linkedin){
- let color = frm.doc.linkedin_post_id ? "green" : "red";
- let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
- html += `
diff --git a/erpnext/healthcare/print_format/encounter_print/encounter_print.json b/erpnext/healthcare/print_format/encounter_print/encounter_print.json
index ec1e0f202a2..3c90adb0a1c 100644
--- a/erpnext/healthcare/print_format/encounter_print/encounter_print.json
+++ b/erpnext/healthcare/print_format/encounter_print/encounter_print.json
@@ -16,7 +16,7 @@
"name": "Encounter Print",
"owner": "Administrator",
"print_format_builder": 0,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"show_section_headings": 0,
"standard": "Yes"
}
\ No newline at end of file
diff --git a/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json b/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json
index e99ce708f46..4819e6d57ac 100644
--- a/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json
+++ b/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json
@@ -16,7 +16,7 @@
"name": "Sample ID Print",
"owner": "Administrator",
"print_format_builder": 0,
- "print_format_type": "Server",
+ "print_format_type": "Jinja",
"show_section_headings": 0,
"standard": "Yes"
}
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py
index b9077301bad..28b60bdcc92 100644
--- a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py
+++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py
@@ -195,4 +195,4 @@ def get_chart_data(data):
chart["fieldtype"] = "Data"
- return chart
\ No newline at end of file
+ return chart
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
index 4b461f1a97d..0a538fdff0d 100644
--- a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
+++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
@@ -25,7 +25,7 @@ class TestInpatientMedicationOrders(unittest.TestCase):
'from_date': getdate(),
'to_date': getdate(),
'patient': '_Test IPD Patient',
- 'service_unit': 'Test Service Unit Ip Occupancy - _TC'
+ 'service_unit': '_Test Service Unit Ip Occupancy - _TC'
}
report = execute(filters)
@@ -42,7 +42,7 @@ class TestInpatientMedicationOrders(unittest.TestCase):
'date': getdate(),
'time': datetime.timedelta(seconds=32400),
'is_completed': 0,
- 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+ 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC'
},
{
'patient': '_Test IPD Patient',
@@ -55,7 +55,7 @@ class TestInpatientMedicationOrders(unittest.TestCase):
'date': getdate(),
'time': datetime.timedelta(seconds=50400),
'is_completed': 0,
- 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+ 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC'
},
{
'patient': '_Test IPD Patient',
@@ -68,7 +68,7 @@ class TestInpatientMedicationOrders(unittest.TestCase):
'date': getdate(),
'time': datetime.timedelta(seconds=75600),
'is_completed': 0,
- 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+ 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC'
}
]
@@ -83,7 +83,7 @@ class TestInpatientMedicationOrders(unittest.TestCase):
'from_date': getdate(),
'to_date': getdate(),
'patient': '_Test IPD Patient',
- 'service_unit': 'Test Service Unit Ip Occupancy - _TC',
+ 'service_unit': '_Test Service Unit Ip Occupancy - _TC',
'show_completed_orders': 0
}
@@ -93,12 +93,12 @@ class TestInpatientMedicationOrders(unittest.TestCase):
def tearDown(self):
if frappe.db.get_value('Patient', self.patient, 'inpatient_record'):
# cleanup - Discharge
- schedule_discharge(frappe.as_json({'patient': self.patient}))
+ schedule_discharge(frappe.as_json({'patient': self.patient, 'discharge_ordered_datetime': now_datetime()}))
self.ip_record.reload()
mark_invoiced_inpatient_occupancy(self.ip_record)
self.ip_record.reload()
- discharge_patient(self.ip_record)
+ discharge_patient(self.ip_record, now_datetime())
for entry in frappe.get_all('Inpatient Medication Entry'):
doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
@@ -119,7 +119,7 @@ def create_records(patient):
ip_record.expected_length_of_stay = 0
ip_record.save()
ip_record.reload()
- service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
+ service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy')
admit_patient(ip_record, service_unit, now_datetime())
ipmo = create_ipmo(patient)
diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.py b/erpnext/healthcare/report/lab_test_report/lab_test_report.py
index 2e59bed0373..ba4ca4172dc 100644
--- a/erpnext/healthcare/report/lab_test_report/lab_test_report.py
+++ b/erpnext/healthcare/report/lab_test_report/lab_test_report.py
@@ -169,7 +169,7 @@ def get_chart_data(data):
'labels': labels,
'datasets': datasets
},
- 'type': 'donut',
+ 'type': 'bar',
'height': 300,
}
diff --git a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py
index 9c35dbb3ea5..9a4840acfea 100644
--- a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py
+++ b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py
@@ -191,4 +191,4 @@ class Analytics(object):
'datasets': []
},
"type": "line"
- }
\ No newline at end of file
+ }
diff --git a/erpnext/healthcare/setup.py b/erpnext/healthcare/setup.py
index bf4df7e4c88..891272ddf81 100644
--- a/erpnext/healthcare/setup.py
+++ b/erpnext/healthcare/setup.py
@@ -292,4 +292,4 @@ def get_patient_history_config():
{"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"},
{"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"}
])
- }
\ No newline at end of file
+ }
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index d3d22c80b67..7c80bdb73ce 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -8,8 +8,7 @@ import frappe
import json
from frappe import _
from frappe.utils.formatters import format_value
-from frappe.utils import time_diff_in_hours, rounded
-from six import string_types
+from frappe.utils import time_diff_in_hours, rounded, cstr
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
@@ -181,9 +180,9 @@ def get_clinical_procedures_to_invoice(patient, company):
service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item')
if not service_item:
- msg = _('Please Configure Clinical Procedure Consumable Item in ')
- msg += '''Healthcare Settings'''
- frappe.throw(msg, title=_('Missing Configuration'))
+ frappe.throw(_('Please configure Clinical Procedure Consumable Item in {0}').format(
+ frappe.utils.get_link_to_form('Healthcare Settings', 'Healthcare Settings')),
+ title=_('Missing Configuration'))
clinical_procedures_to_invoice.append({
'reference_type': 'Clinical Procedure',
@@ -312,7 +311,7 @@ def get_therapy_sessions_to_invoice(patient, company):
@frappe.whitelist()
def get_service_item_and_practitioner_charge(doc):
- if isinstance(doc, string_types):
+ if isinstance(doc, str):
doc = json.loads(doc)
doc = frappe.get_doc(doc)
@@ -543,58 +542,43 @@ def get_drugs_to_invoice(encounter):
@frappe.whitelist()
-def get_children(doctype, parent, company, is_root=False):
- parent_fieldname = "parent_" + doctype.lower().replace(" ", "_")
+def get_children(doctype, parent=None, company=None, is_root=False):
+ parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_')
fields = [
- "name as value",
- "is_group as expandable",
- "lft",
- "rgt"
+ 'name as value',
+ 'is_group as expandable',
+ 'lft',
+ 'rgt'
]
- # fields = [ "name", "is_group", "lft", "rgt" ]
- filters = [["ifnull(`{0}`,'')".format(parent_fieldname), "=", "" if is_root else parent]]
+
+ filters = [["ifnull(`{0}`,'')".format(parent_fieldname),
+ '=', '' if is_root else parent]]
if is_root:
- fields += ["service_unit_type"] if doctype == "Healthcare Service Unit" else []
- filters.append(["company", "=", company])
-
+ fields += ['service_unit_type'] if doctype == 'Healthcare Service Unit' else []
+ filters.append(['company', '=', company])
else:
- fields += ["service_unit_type", "allow_appointments", "inpatient_occupancy", "occupancy_status"] if doctype == "Healthcare Service Unit" else []
- fields += [parent_fieldname + " as parent"]
+ fields += ['service_unit_type', 'allow_appointments', 'inpatient_occupancy',
+ 'occupancy_status'] if doctype == 'Healthcare Service Unit' else []
+ fields += [parent_fieldname + ' as parent']
- hc_service_units = frappe.get_list(doctype, fields=fields, filters=filters)
+ service_units = frappe.get_list(doctype, fields=fields, filters=filters)
+ for each in service_units:
+ if each['expandable'] == 1: # group node
+ available_count = frappe.db.count('Healthcare Service Unit', filters={
+ 'parent_healthcare_service_unit': each['value'],
+ 'inpatient_occupancy': 1})
- if doctype == "Healthcare Service Unit":
- for each in hc_service_units:
- occupancy_msg = ""
- if each["expandable"] == 1:
- occupied = False
- vacant = False
- child_list = frappe.db.sql(
- '''
- SELECT
- name, occupancy_status
- FROM
- `tabHealthcare Service Unit`
- WHERE
- inpatient_occupancy = 1
- and lft > %s and rgt < %s
- ''', (each['lft'], each['rgt']))
+ if available_count > 0:
+ occupied_count = frappe.db.count('Healthcare Service Unit', {
+ 'parent_healthcare_service_unit': each['value'],
+ 'inpatient_occupancy': 1,
+ 'occupancy_status': 'Occupied'})
+ # set occupancy status of group node
+ each['occupied_of_available'] = str(
+ occupied_count) + ' Occupied of ' + str(available_count)
- for child in child_list:
- if not occupied:
- occupied = 0
- if child[1] == "Occupied":
- occupied += 1
- if not vacant:
- vacant = 0
- if child[1] == "Vacant":
- vacant += 1
- if vacant and occupied:
- occupancy_total = vacant + occupied
- occupancy_msg = str(occupied) + " Occupied out of " + str(occupancy_total)
- each["occupied_out_of_vacant"] = occupancy_msg
- return hc_service_units
+ return service_units
@frappe.whitelist()
@@ -622,98 +606,181 @@ def render_docs_as_html(docs):
@frappe.whitelist()
def render_doc_as_html(doctype, docname, exclude_fields = []):
- #render document as html, three column layout will break
+ """
+ Render document as HTML
+ """
+
doc = frappe.get_doc(doctype, docname)
meta = frappe.get_meta(doctype)
- doc_html = "
"
- section_html = ''
- section_label = ''
- html = ''
- sec_on = False
+ doc_html = section_html = section_label = html = ""
+ sec_on = has_data = False
col_on = 0
- has_data = False
+
for df in meta.fields:
- #on section break append append previous section and html to doc html
+ # on section break append previous section and html to doc html
if df.fieldtype == "Section Break":
if has_data and col_on and sec_on:
doc_html += section_html + html + "
"
+
elif has_data and not col_on and sec_on:
- doc_html += "
" \
- + section_html + html +"
"
+ doc_html += """
+
+
+
+ {0}
+
+
+
+
+ {1} {2}
+
+
+ """.format(section_label, section_html, html)
+
+ # close divs for columns
while col_on:
doc_html += "
"
col_on -= 1
+
sec_on = True
- has_data= False
+ has_data = False
col_on = 0
- section_html = ''
- html = ''
+ section_html = html = ""
+
if df.label:
section_label = df.label
continue
- #on column break append html to section html or doc html
+
+ # on column break append html to section html or doc html
if df.fieldtype == "Column Break":
- if sec_on and has_data:
- section_html += "
" + section_label + "" + html + "
"
- elif has_data:
- doc_html += "
" + html + "
"
- elif sec_on and not col_on:
- section_html += "
"
- html = ''
+ if sec_on and not col_on and has_data:
+ section_html += """
+
+
+ """.format(html)
+
+ html = ""
col_on += 1
+
if df.label:
- html += ' ' + df.label
+ html += " " + df.label
continue
- #on table iterate in items and create table based on in_list_view, append to section html or doc html
- if df.fieldtype == 'Table':
+
+ # on table iterate through items and create table
+ # based on the in_list_view property
+ # append to section html or doc html
+ if df.fieldtype == "Table":
items = doc.get(df.fieldname)
- if not items: continue
+ if not items:
+ continue
child_meta = frappe.get_meta(df.options)
- if not has_data : has_data = True
- table_head = ''
- table_row = ''
+
+ if not has_data:
+ has_data = True
+ table_head = table_row = ""
create_head = True
+
for item in items:
- table_row += '
'
+ table_row += "
"
for cdf in child_meta.fields:
if cdf.in_list_view:
if create_head:
- table_head += '
+ """.format(table_head, table_row)
else:
- html += "
" \
- + table_head + table_row + "
"
+ html += """
+
+ {0} {1}
+
+ """.format(table_head, table_row)
continue
- #on other field types add label and value to html
+ # on any other field type add label and value to html
if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields:
- if doc.get(df.fieldname):
- formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc)
- html += ' {0} : {1}'.format(df.label or df.fieldname, formatted_value)
+ formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc)
+ html += " {0} : {1}".format(df.label or df.fieldname, formatted_value)
if not has_data : has_data = True
if sec_on and col_on and has_data:
- doc_html += section_html + html + '
'
+ doc_html += section_html + html + "
"
elif sec_on and not col_on and has_data:
- doc_html += "
" \
- + section_html + html +'
'
- if doc_html:
- doc_html = "
" %(doctype, docname) + doc_html + '
'
+ doc_html += """
+
+
+ {0} {1}
+
+
+ """.format(section_html, html)
- return {'html': doc_html}
+ return {"html": doc_html}
+
+
+def update_address_links(address, method):
+ '''
+ Hook validate Address
+ If Patient is linked in Address, also link the associated Customer
+ '''
+ if 'Healthcare' not in frappe.get_active_domains():
+ return
+
+ patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', address.links))
+
+ for link in patient_links:
+ customer = frappe.db.get_value('Patient', link.get('link_name'), 'customer')
+ if customer and not address.has_link('Customer', customer):
+ address.append('links', dict(link_doctype = 'Customer', link_name = customer))
+
+
+def update_patient_email_and_phone_numbers(contact, method):
+ '''
+ Hook validate Contact
+ Update linked Patients' primary mobile and phone numbers
+ '''
+ if 'Healthcare' not in frappe.get_active_domains():
+ return
+
+ if contact.is_primary_contact and (contact.email_id or contact.mobile_no or contact.phone):
+ patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', contact.links))
+
+ for link in patient_links:
+ contact_details = frappe.db.get_value('Patient', link.get('link_name'), ['email', 'mobile', 'phone'], as_dict=1)
+ if contact.email_id and contact.email_id != contact_details.get('email'):
+ frappe.db.set_value('Patient', link.get('link_name'), 'email', contact.email_id)
+ if contact.mobile_no and contact.mobile_no != contact_details.get('mobile'):
+ frappe.db.set_value('Patient', link.get('link_name'), 'mobile', contact.mobile_no)
+ if contact.phone and contact.phone != contact_details.get('phone'):
+ frappe.db.set_value('Patient', link.get('link_name'), 'phone', contact.phone)
diff --git a/erpnext/healthcare/web_form/patient_registration/patient_registration.js b/erpnext/healthcare/web_form/patient_registration/patient_registration.js
index 7da3f1fb41c..f09e5409192 100644
--- a/erpnext/healthcare/web_form/patient_registration/patient_registration.js
+++ b/erpnext/healthcare/web_form/patient_registration/patient_registration.js
@@ -1,3 +1,3 @@
frappe.ready(function() {
// bind events here
-});
\ No newline at end of file
+});
diff --git a/erpnext/healthcare/workspace/healthcare/healthcare.json b/erpnext/healthcare/workspace/healthcare/healthcare.json
index b93dda2e879..ea5c61e4da9 100644
--- a/erpnext/healthcare/workspace/healthcare/healthcare.json
+++ b/erpnext/healthcare/workspace/healthcare/healthcare.json
@@ -66,46 +66,6 @@
"onboard": 0,
"type": "Link"
},
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Healthcare Service Unit Type",
- "link_to": "Healthcare Service Unit Type",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Healthcare Service Unit",
- "link_to": "Healthcare Service Unit",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Medical Code Standard",
- "link_to": "Medical Code Standard",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Medical Code",
- "link_to": "Medical Code",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
{
"hidden": 0,
"is_query_report": 0,
@@ -163,6 +123,26 @@
"onboard": 0,
"type": "Link"
},
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Complaint",
+ "link_to": "Complaint",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Diagnosis",
+ "link_to": "Diagnosis",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
{
"hidden": 0,
"is_query_report": 0,
@@ -210,26 +190,6 @@
"onboard": 0,
"type": "Link"
},
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Complaint",
- "link_to": "Complaint",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Diagnosis",
- "link_to": "Diagnosis",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
{
"dependencies": "",
"hidden": 0,
@@ -240,6 +200,62 @@
"onboard": 0,
"type": "Link"
},
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Facility Management",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Healthcare Service Unit Type",
+ "link_to": "Healthcare Service Unit Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Healthcare Service Unit",
+ "link_to": "Healthcare Service Unit",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Medical Coding",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Medical Code Standard",
+ "link_to": "Medical Code Standard",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Medical Code",
+ "link_to": "Medical Code",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
{
"hidden": 0,
"is_query_report": 0,
@@ -304,6 +320,16 @@
"onboard": 0,
"type": "Link"
},
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Dosage Form",
+ "link_to": "Dosage Form",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
{
"hidden": 0,
"is_query_report": 0,
@@ -332,11 +358,36 @@
"type": "Link"
},
{
- "dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Dosage Form",
- "link_to": "Dosage Form",
+ "label": "Inpatient",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Inpatient Medication Order",
+ "link_to": "Inpatient Medication Order",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Inpatient Record",
+ "link_to": "Inpatient Record",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Inpatient Medication Entry",
+ "link_to": "Inpatient Medication Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
@@ -483,7 +534,7 @@
"type": "Link"
}
],
- "modified": "2020-12-01 13:38:34.841396",
+ "modified": "2021-01-30 19:35:45.316999",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare",
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index e6b6cc4a8a0..2385b7cbade 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -243,7 +243,7 @@ doc_events = {
"on_update": ["erpnext.hr.doctype.employee.employee.update_user_permissions",
"erpnext.portal.utils.set_default_role"]
},
- ("Sales Taxes and Charges Template", 'Price List'): {
+ "Sales Taxes and Charges Template": {
"on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
},
"Website Settings": {
@@ -282,7 +282,12 @@ doc_events = {
"on_trash": "erpnext.regional.check_deletion_permission"
},
'Address': {
- 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category']
+ 'validate': [
+ 'erpnext.regional.india.utils.validate_gstin_for_india',
+ 'erpnext.regional.italy.utils.set_state_code',
+ 'erpnext.regional.india.utils.update_gst_category',
+ 'erpnext.healthcare.utils.update_address_links'
+ ],
},
'Supplier': {
'validate': 'erpnext.regional.india.utils.validate_pan_for_india'
@@ -293,13 +298,16 @@ doc_events = {
"Contact": {
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
- "validate": "erpnext.crm.utils.update_lead_phone_numbers"
+ "validate": ["erpnext.crm.utils.update_lead_phone_numbers", "erpnext.healthcare.utils.update_patient_email_and_phone_numbers"]
},
"Email Unsubscribe": {
"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
},
('Quotation', 'Sales Order', 'Sales Invoice'): {
'validate': ["erpnext.erpnext_integrations.taxjar_integration.set_sales_tax"]
+ },
+ "Company": {
+ "on_trash": "erpnext.regional.india.utils.delete_gst_settings_for_company"
}
}
@@ -345,7 +353,8 @@ scheduler_events = {
"erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity",
"erpnext.controllers.accounts_controller.update_invoice_status",
"erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year",
- "erpnext.hr.doctype.employee.employee.send_birthday_reminders",
+ "erpnext.hr.doctype.employee.employee_reminders.send_work_anniversary_reminders",
+ "erpnext.hr.doctype.employee.employee_reminders.send_birthday_reminders",
"erpnext.projects.doctype.task.task.set_tasks_as_overdue",
"erpnext.assets.doctype.asset.depreciation.post_depreciation_entries",
"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.send_summary",
@@ -377,6 +386,12 @@ scheduler_events = {
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
"erpnext.crm.doctype.lead.lead.daily_open_lead"
],
+ "weekly": [
+ "erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"
+ ],
+ "monthly": [
+ "erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"
+ ],
"monthly_long": [
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_demand_loans"
diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.py b/erpnext/hotels/doctype/hotel_room/hotel_room.py
index 8471aee4a03..6a2fc02574f 100644
--- a/erpnext/hotels/doctype/hotel_room/hotel_room.py
+++ b/erpnext/hotels/doctype/hotel_room/hotel_room.py
@@ -10,4 +10,4 @@ class HotelRoom(Document):
def validate(self):
if not self.capacity:
self.capacity, self.extra_bed_capacity = frappe.db.get_value('Hotel Room Type',
- self.hotel_room_type, ['capacity', 'extra_bed_capacity'])
\ No newline at end of file
+ self.hotel_room_type, ['capacity', 'extra_bed_capacity'])
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js
index 7f7322cf4b6..7bde292a2bc 100644
--- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js
+++ b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js
@@ -6,4 +6,4 @@ frappe.views.calendar["Hotel Room Reservation"] = {
"title": "guest_name",
"status": "status"
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py
index f77d43b3143..259edb9c06d 100644
--- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py
+++ b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py
@@ -30,4 +30,4 @@ def get_data(filters):
out.append([room_type.name, total_booked])
- return out
\ No newline at end of file
+ return out
diff --git a/erpnext/hr/doctype/appraisal/appraisal.js b/erpnext/hr/doctype/appraisal/appraisal.js
index 1a30ceac6d3..50612b923ef 100644
--- a/erpnext/hr/doctype/appraisal/appraisal.js
+++ b/erpnext/hr/doctype/appraisal/appraisal.js
@@ -15,7 +15,7 @@ frappe.ui.form.on('Appraisal', {
frm.set_value('status', 'Draft');
}
},
-
+
kra_template: function(frm) {
frm.doc.goals = [];
erpnext.utils.map_current_doc({
diff --git a/erpnext/hr/doctype/appraisal/test_appraisal.js b/erpnext/hr/doctype/appraisal/test_appraisal.js
index 9ca17e2e226..fb1354c3f6b 100644
--- a/erpnext/hr/doctype/appraisal/test_appraisal.js
+++ b/erpnext/hr/doctype/appraisal/test_appraisal.js
@@ -55,4 +55,3 @@ QUnit.test("Test: Expense Claim [HR]", function (assert) {
() => done()
]);
});
-
diff --git a/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py b/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py
index a6868ee2b1c..11d9f3944d5 100644
--- a/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py
+++ b/erpnext/hr/doctype/appraisal_goal/appraisal_goal.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class AppraisalGoal(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py b/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py
index 309427e30c2..392b370e6c3 100644
--- a/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py
+++ b/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Appraisal']
},
],
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js
index 0403cad0683..3eb64e08501 100644
--- a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js
+++ b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js
@@ -27,4 +27,3 @@ QUnit.test("Test: Appraisal Template [HR]", function (assert) {
() => done()
]);
});
-
diff --git a/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py b/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py
index ca58e0c3202..b3c5704fa53 100644
--- a/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py
+++ b/erpnext/hr/doctype/appraisal_template_goal/appraisal_template_goal.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class AppraisalTemplateGoal(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index f79f0fe4180..c1a7c8f88a5 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -135,7 +135,7 @@ def mark_attendance(employee, attendance_date, status, shift=None, leave_type=No
def mark_bulk_attendance(data):
import json
from pprint import pprint
- if isinstance(data, frappe.string_types):
+ if isinstance(data, str):
data = json.loads(data)
data = frappe._dict(data)
company = frappe.get_value('Employee', data.employee, 'company')
diff --git a/erpnext/hr/doctype/attendance/attendance_calendar.js b/erpnext/hr/doctype/attendance/attendance_calendar.js
index 45664896965..d9f6d2eb3eb 100644
--- a/erpnext/hr/doctype/attendance/attendance_calendar.js
+++ b/erpnext/hr/doctype/attendance/attendance_calendar.js
@@ -9,4 +9,4 @@ frappe.views.calendar["Attendance"] = {
}
},
get_events_method: "erpnext.hr.doctype.attendance.attendance.get_events"
-};
\ No newline at end of file
+};
diff --git a/erpnext/hr/doctype/attendance/test_attendance.js b/erpnext/hr/doctype/attendance/test_attendance.js
index 8f30e8cc161..b3e7fef02a0 100644
--- a/erpnext/hr/doctype/attendance/test_attendance.js
+++ b/erpnext/hr/doctype/attendance/test_attendance.js
@@ -36,4 +36,4 @@ QUnit.test("Test: Attendance [HR]", function (assert) {
"attendance for Present day is marked"),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py b/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py
index cfdd6d3aefb..2d3eb000119 100644
--- a/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py
+++ b/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py
@@ -8,4 +8,4 @@ def get_data():
'items': ['Attendance']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py
index 3c42bd9fc35..9e668aa72fb 100644
--- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py
+++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py
@@ -15,7 +15,11 @@ class TestAttendanceRequest(unittest.TestCase):
for doctype in ["Attendance Request", "Attendance"]:
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
+ def tearDown(self):
+ frappe.db.rollback()
+
def test_on_duty_attendance_request(self):
+ "Test creation/updation of Attendace from Attendance Request, on duty."
today = nowdate()
employee = get_employee()
attendance_request = frappe.new_doc("Attendance Request")
@@ -26,17 +30,36 @@ class TestAttendanceRequest(unittest.TestCase):
attendance_request.company = "_Test Company"
attendance_request.insert()
attendance_request.submit()
- attendance = frappe.get_doc('Attendance', {
- 'employee': employee.name,
- 'attendance_date': date(date.today().year, 1, 1),
- 'docstatus': 1
- })
- self.assertEqual(attendance.status, 'Present')
+
+ attendance = frappe.db.get_value(
+ "Attendance",
+ filters={
+ "attendance_request": attendance_request.name,
+ "attendance_date": date(date.today().year, 1, 1)
+ },
+ fieldname=["status", "docstatus"],
+ as_dict=True
+ )
+ self.assertEqual(attendance.status, "Present")
+ self.assertEqual(attendance.docstatus, 1)
+
+ # cancelling attendance request cancels linked attendances
attendance_request.cancel()
- attendance.reload()
- self.assertEqual(attendance.docstatus, 2)
+
+ # cancellation alters docname
+ # fetch attendance value again to avoid stale docname
+ attendance_docstatus = frappe.db.get_value(
+ "Attendance",
+ filters={
+ "attendance_request": attendance_request.name,
+ "attendance_date": date(date.today().year, 1, 1)
+ },
+ fieldname="docstatus"
+ )
+ self.assertEqual(attendance_docstatus, 2)
def test_work_from_home_attendance_request(self):
+ "Test creation/updation of Attendace from Attendance Request, work from home."
today = nowdate()
employee = get_employee()
attendance_request = frappe.new_doc("Attendance Request")
@@ -47,15 +70,30 @@ class TestAttendanceRequest(unittest.TestCase):
attendance_request.company = "_Test Company"
attendance_request.insert()
attendance_request.submit()
- attendance = frappe.get_doc('Attendance', {
- 'employee': employee.name,
- 'attendance_date': date(date.today().year, 1, 1),
- 'docstatus': 1
- })
- self.assertEqual(attendance.status, 'Work From Home')
+
+ attendance_status = frappe.db.get_value(
+ "Attendance",
+ filters={
+ "attendance_request": attendance_request.name,
+ "attendance_date": date(date.today().year, 1, 1)
+ },
+ fieldname="status"
+ )
+ self.assertEqual(attendance_status, 'Work From Home')
+
attendance_request.cancel()
- attendance.reload()
- self.assertEqual(attendance.docstatus, 2)
+
+ # cancellation alters docname
+ # fetch attendance value again to avoid stale docname
+ attendance_docstatus = frappe.db.get_value(
+ "Attendance",
+ filters={
+ "attendance_request": attendance_request.name,
+ "attendance_date": date(date.today().year, 1, 1)
+ },
+ fieldname="docstatus"
+ )
+ self.assertEqual(attendance_docstatus, 2)
def get_employee():
return frappe.get_doc("Employee", "_T-Employee-00001")
diff --git a/erpnext/hr/doctype/branch/branch.py b/erpnext/hr/doctype/branch/branch.py
index fab2ffc1a37..a847c8e2174 100644
--- a/erpnext/hr/doctype/branch/branch.py
+++ b/erpnext/hr/doctype/branch/branch.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class Branch(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/branch/test_branch.js b/erpnext/hr/doctype/branch/test_branch.js
index c315385f116..82a6ae103ee 100644
--- a/erpnext/hr/doctype/branch/test_branch.js
+++ b/erpnext/hr/doctype/branch/test_branch.js
@@ -20,4 +20,4 @@ QUnit.test("Test: Branch [HR]", function (assert) {
'name of branch correctly saved'),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/branch/test_branch.py b/erpnext/hr/doctype/branch/test_branch.py
index 5ba02b36b8a..807698ba0a2 100644
--- a/erpnext/hr/doctype/branch/test_branch.py
+++ b/erpnext/hr/doctype/branch/test_branch.py
@@ -4,4 +4,4 @@ from __future__ import unicode_literals
import frappe
-test_records = frappe.get_test_records('Branch')
\ No newline at end of file
+test_records = frappe.get_test_records('Branch')
diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
index 0d7fded921b..3db81654a65 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
@@ -8,7 +8,7 @@ from frappe import _
from frappe.utils import date_diff, add_days, getdate, cint, format_date
from frappe.model.document import Document
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \
- get_holidays_for_employee, create_additional_leave_ledger_entry
+ create_additional_leave_ledger_entry, get_holiday_dates_for_employee
class CompensatoryLeaveRequest(Document):
@@ -39,7 +39,7 @@ class CompensatoryLeaveRequest(Document):
frappe.throw(_("You are not present all day(s) between compensatory leave request days"))
def validate_holidays(self):
- holidays = get_holidays_for_employee(self.employee, self.work_from_date, self.work_end_date)
+ holidays = get_holiday_dates_for_employee(self.employee, self.work_from_date, self.work_end_date)
if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
if date_diff(self.work_end_date, self.work_from_date):
msg = _("The days between {0} to {1} are not valid holidays.").format(frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date)))
diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js
index d2ceb8bd527..15335171473 100644
--- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js
+++ b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js
@@ -20,4 +20,4 @@ QUnit.test("test: Daily Work Summary", function (assert) {
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/department/department_tree.js b/erpnext/hr/doctype/department/department_tree.js
index 52d864bc0e6..5c7726de6a6 100644
--- a/erpnext/hr/doctype/department/department_tree.js
+++ b/erpnext/hr/doctype/department/department_tree.js
@@ -25,4 +25,4 @@ frappe.treeview_settings["Department"] = {
onload: function(treeview) {
treeview.make_tree();
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/hr/doctype/department/test_department.js b/erpnext/hr/doctype/department/test_department.js
index 3a571f76535..e73779c97c6 100644
--- a/erpnext/hr/doctype/department/test_department.js
+++ b/erpnext/hr/doctype/department/test_department.js
@@ -20,4 +20,4 @@ QUnit.test("Test: Department [HR]", function (assert) {
'name of department correctly saved'),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/department/test_department.py b/erpnext/hr/doctype/department/test_department.py
index 2eeca26e303..e4f6645ee42 100644
--- a/erpnext/hr/doctype/department/test_department.py
+++ b/erpnext/hr/doctype/department/test_department.py
@@ -21,4 +21,4 @@ def create_department(department_name, parent_department=None):
return doc
-test_records = frappe.get_test_records('Department')
\ No newline at end of file
+test_records = frappe.get_test_records('Department')
diff --git a/erpnext/hr/doctype/designation/designation.py b/erpnext/hr/doctype/designation/designation.py
index efd864ad593..a3f84aab5f0 100644
--- a/erpnext/hr/doctype/designation/designation.py
+++ b/erpnext/hr/doctype/designation/designation.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class Designation(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/designation/test_designation.js b/erpnext/hr/doctype/designation/test_designation.js
index 45c34171911..00adf8293f7 100644
--- a/erpnext/hr/doctype/designation/test_designation.js
+++ b/erpnext/hr/doctype/designation/test_designation.js
@@ -20,4 +20,4 @@ QUnit.test("Test: Designation [HR]", function (assert) {
'name of designation correctly saved'),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/designation/test_designation.py b/erpnext/hr/doctype/designation/test_designation.py
index 3b300941a65..2778862a1c2 100644
--- a/erpnext/hr/doctype/designation/test_designation.py
+++ b/erpnext/hr/doctype/designation/test_designation.py
@@ -17,4 +17,4 @@ def create_designation(**args):
"description": args.description or "_Test description"
})
designation.save()
- return designation
\ No newline at end of file
+ return designation
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 5ca47560b10..643f3da2ff7 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -1,7 +1,5 @@
# 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 getdate, validate_email_address, today, add_years, cstr
@@ -9,7 +7,6 @@ from frappe.model.naming import set_name_by_naming_series
from frappe import throw, _, scrub
from frappe.permissions import add_user_permission, remove_user_permission, \
set_user_permission_if_allowed, has_permission, get_doc_permissions
-from frappe.model.document import Document
from erpnext.utilities.transaction_base import delete_events
from frappe.utils.nestedset import NestedSet
@@ -286,94 +283,8 @@ def update_user_permissions(doc, method):
employee = frappe.get_doc("Employee", {"user_id": doc.name})
employee.update_user_permissions()
-def send_birthday_reminders():
- """Send Employee birthday reminders if no 'Stop Birthday Reminders' is not set."""
- if int(frappe.db.get_single_value("HR Settings", "stop_birthday_reminders") or 0):
- return
-
- employees_born_today = get_employees_who_are_born_today()
-
- for company, birthday_persons in employees_born_today.items():
- employee_emails = get_all_employee_emails(company)
- birthday_person_emails = [get_employee_email(doc) for doc in birthday_persons]
- recipients = list(set(employee_emails) - set(birthday_person_emails))
-
- reminder_text, message = get_birthday_reminder_text_and_message(birthday_persons)
- send_birthday_reminder(recipients, reminder_text, birthday_persons, message)
-
- if len(birthday_persons) > 1:
- # special email for people sharing birthdays
- for person in birthday_persons:
- person_email = person["user_id"] or person["personal_email"] or person["company_email"]
- others = [d for d in birthday_persons if d != person]
- reminder_text, message = get_birthday_reminder_text_and_message(others)
- send_birthday_reminder(person_email, reminder_text, others, message)
-
def get_employee_email(employee_doc):
- return employee_doc["user_id"] or employee_doc["personal_email"] or employee_doc["company_email"]
-
-def get_birthday_reminder_text_and_message(birthday_persons):
- if len(birthday_persons) == 1:
- birthday_person_text = birthday_persons[0]['name']
- else:
- # converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
- person_names = [d['name'] for d in birthday_persons]
- last_person = person_names[-1]
- birthday_person_text = ", ".join(person_names[:-1])
- birthday_person_text = _("{} & {}").format(birthday_person_text, last_person)
-
- reminder_text = _("Today is {0}'s birthday 🎉").format(birthday_person_text)
- message = _("A friendly reminder of an important date for our team.")
- message += " "
- message += _("Everyone, let’s congratulate {0} on their birthday.").format(birthday_person_text)
-
- return reminder_text, message
-
-def send_birthday_reminder(recipients, reminder_text, birthday_persons, message):
- frappe.sendmail(
- recipients=recipients,
- subject=_("Birthday Reminder"),
- template="birthday_reminder",
- args=dict(
- reminder_text=reminder_text,
- birthday_persons=birthday_persons,
- message=message,
- ),
- header=_("Birthday Reminder 🎂")
- )
-
-def get_employees_who_are_born_today():
- """Get all employee born today & group them based on their company"""
- from collections import defaultdict
- employees_born_today = frappe.db.multisql({
- "mariadb": """
- SELECT `personal_email`, `company`, `company_email`, `user_id`, `employee_name` AS 'name', `image`
- FROM `tabEmployee`
- WHERE
- DAY(date_of_birth) = DAY(%(today)s)
- AND
- MONTH(date_of_birth) = MONTH(%(today)s)
- AND
- `status` = 'Active'
- """,
- "postgres": """
- SELECT "personal_email", "company", "company_email", "user_id", "employee_name" AS 'name', "image"
- FROM "tabEmployee"
- WHERE
- DATE_PART('day', "date_of_birth") = date_part('day', %(today)s)
- AND
- DATE_PART('month', "date_of_birth") = date_part('month', %(today)s)
- AND
- "status" = 'Active'
- """,
- }, dict(today=today()), as_dict=1)
-
- grouped_employees = defaultdict(lambda: [])
-
- for employee_doc in employees_born_today:
- grouped_employees[employee_doc.get('company')].append(employee_doc)
-
- return grouped_employees
+ return employee_doc.get("user_id") or employee_doc.get("personal_email") or employee_doc.get("company_email")
def get_holiday_list_for_employee(employee, raise_exception=True):
if employee:
@@ -390,17 +301,40 @@ def get_holiday_list_for_employee(employee, raise_exception=True):
return holiday_list
-def is_holiday(employee, date=None, raise_exception=True):
- '''Returns True if given Employee has an holiday on the given date
- :param employee: Employee `name`
- :param date: Date to check. Will check for today if None'''
+def is_holiday(employee, date=None, raise_exception=True, only_non_weekly=False, with_description=False):
+ '''
+ Returns True if given Employee has an holiday on the given date
+ :param employee: Employee `name`
+ :param date: Date to check. Will check for today if None
+ :param raise_exception: Raise an exception if no holiday list found, default is True
+ :param only_non_weekly: Check only non-weekly holidays, default is False
+ '''
holiday_list = get_holiday_list_for_employee(employee, raise_exception)
if not date:
date = today()
- if holiday_list:
- return frappe.get_all('Holiday List', dict(name=holiday_list, holiday_date=date)) and True or False
+ if not holiday_list:
+ return False
+
+ filters = {
+ 'parent': holiday_list,
+ 'holiday_date': date
+ }
+ if only_non_weekly:
+ filters['weekly_off'] = False
+
+ holidays = frappe.get_all(
+ 'Holiday',
+ fields=['description'],
+ filters=filters,
+ pluck='description'
+ )
+
+ if with_description:
+ return len(holidays) > 0, holidays
+
+ return len(holidays) > 0
@frappe.whitelist()
def deactivate_sales_person(status = None, employee = None):
@@ -503,7 +437,6 @@ def get_children(doctype, parent=None, company=None, is_root=False, is_tree=Fals
return employees
-
def on_doctype_update():
frappe.db.add_index("Employee", ["lft", "rgt"])
@@ -520,4 +453,4 @@ def has_upload_permission(doc, ptype='read', user=None):
user = frappe.session.user
if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype):
return True
- return doc.user_id == user
\ No newline at end of file
+ return doc.user_id == user
diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py
new file mode 100644
index 00000000000..2155c027a9b
--- /dev/null
+++ b/erpnext/hr/doctype/employee/employee_reminders.py
@@ -0,0 +1,247 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe import _
+from frappe.utils import comma_sep, getdate, today, add_months, add_days
+from erpnext.hr.doctype.employee.employee import get_all_employee_emails, get_employee_email
+from erpnext.hr.utils import get_holidays_for_employee
+
+# -----------------
+# HOLIDAY REMINDERS
+# -----------------
+def send_reminders_in_advance_weekly():
+ to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders") or 1)
+ frequency = frappe.db.get_single_value("HR Settings", "frequency")
+ if not (to_send_in_advance and frequency == "Weekly"):
+ return
+
+ send_advance_holiday_reminders("Weekly")
+
+def send_reminders_in_advance_monthly():
+ to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders") or 1)
+ frequency = frappe.db.get_single_value("HR Settings", "frequency")
+ if not (to_send_in_advance and frequency == "Monthly"):
+ return
+
+ send_advance_holiday_reminders("Monthly")
+
+def send_advance_holiday_reminders(frequency):
+ """Send Holiday Reminders in Advance to Employees
+ `frequency` (str): 'Weekly' or 'Monthly'
+ """
+ if frequency == "Weekly":
+ start_date = getdate()
+ end_date = add_days(getdate(), 7)
+ elif frequency == "Monthly":
+ # Sent on 1st of every month
+ start_date = getdate()
+ end_date = add_months(getdate(), 1)
+ else:
+ return
+
+ employees = frappe.db.get_all('Employee', pluck='name')
+ for employee in employees:
+ holidays = get_holidays_for_employee(
+ employee,
+ start_date, end_date,
+ only_non_weekly=True,
+ raise_exception=False
+ )
+
+ if not (holidays is None):
+ send_holidays_reminder_in_advance(employee, holidays)
+
+def send_holidays_reminder_in_advance(employee, holidays):
+ employee_doc = frappe.get_doc('Employee', employee)
+ employee_email = get_employee_email(employee_doc)
+ frequency = frappe.db.get_single_value("HR Settings", "frequency")
+
+ email_header = _("Holidays this Month.") if frequency == "Monthly" else _("Holidays this Week.")
+ frappe.sendmail(
+ recipients=[employee_email],
+ subject=_("Upcoming Holidays Reminder"),
+ template="holiday_reminder",
+ args=dict(
+ reminder_text=_("Hey {}! This email is to remind you about the upcoming holidays.").format(employee_doc.get('first_name')),
+ message=_("Below is the list of upcoming holidays for you:"),
+ advance_holiday_reminder=True,
+ holidays=holidays,
+ frequency=frequency[:-2]
+ ),
+ header=email_header
+ )
+
+# ------------------
+# BIRTHDAY REMINDERS
+# ------------------
+def send_birthday_reminders():
+ """Send Employee birthday reminders if no 'Stop Birthday Reminders' is not set."""
+ to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders") or 1)
+ if not to_send:
+ return
+
+ employees_born_today = get_employees_who_are_born_today()
+
+ for company, birthday_persons in employees_born_today.items():
+ employee_emails = get_all_employee_emails(company)
+ birthday_person_emails = [get_employee_email(doc) for doc in birthday_persons]
+ recipients = list(set(employee_emails) - set(birthday_person_emails))
+
+ reminder_text, message = get_birthday_reminder_text_and_message(birthday_persons)
+ send_birthday_reminder(recipients, reminder_text, birthday_persons, message)
+
+ if len(birthday_persons) > 1:
+ # special email for people sharing birthdays
+ for person in birthday_persons:
+ person_email = person["user_id"] or person["personal_email"] or person["company_email"]
+ others = [d for d in birthday_persons if d != person]
+ reminder_text, message = get_birthday_reminder_text_and_message(others)
+ send_birthday_reminder(person_email, reminder_text, others, message)
+
+def get_birthday_reminder_text_and_message(birthday_persons):
+ if len(birthday_persons) == 1:
+ birthday_person_text = birthday_persons[0]['name']
+ else:
+ # converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
+ person_names = [d['name'] for d in birthday_persons]
+ birthday_person_text = comma_sep(person_names, frappe._("{0} & {1}"), False)
+
+ reminder_text = _("Today is {0}'s birthday 🎉").format(birthday_person_text)
+ message = _("A friendly reminder of an important date for our team.")
+ message += " "
+ message += _("Everyone, let’s congratulate {0} on their birthday.").format(birthday_person_text)
+
+ return reminder_text, message
+
+def send_birthday_reminder(recipients, reminder_text, birthday_persons, message):
+ frappe.sendmail(
+ recipients=recipients,
+ subject=_("Birthday Reminder"),
+ template="birthday_reminder",
+ args=dict(
+ reminder_text=reminder_text,
+ birthday_persons=birthday_persons,
+ message=message,
+ ),
+ header=_("Birthday Reminder 🎂")
+ )
+
+def get_employees_who_are_born_today():
+ """Get all employee born today & group them based on their company"""
+ return get_employees_having_an_event_today("birthday")
+
+def get_employees_having_an_event_today(event_type):
+ """Get all employee who have `event_type` today
+ & group them based on their company. `event_type`
+ can be `birthday` or `work_anniversary`"""
+
+ from collections import defaultdict
+
+ # Set column based on event type
+ if event_type == 'birthday':
+ condition_column = 'date_of_birth'
+ elif event_type == 'work_anniversary':
+ condition_column = 'date_of_joining'
+ else:
+ return
+
+ employees_born_today = frappe.db.multisql({
+ "mariadb": f"""
+ SELECT `personal_email`, `company`, `company_email`, `user_id`, `employee_name` AS 'name', `image`, `date_of_joining`
+ FROM `tabEmployee`
+ WHERE
+ DAY({condition_column}) = DAY(%(today)s)
+ AND
+ MONTH({condition_column}) = MONTH(%(today)s)
+ AND
+ `status` = 'Active'
+ """,
+ "postgres": f"""
+ SELECT "personal_email", "company", "company_email", "user_id", "employee_name" AS 'name', "image"
+ FROM "tabEmployee"
+ WHERE
+ DATE_PART('day', {condition_column}) = date_part('day', %(today)s)
+ AND
+ DATE_PART('month', {condition_column}) = date_part('month', %(today)s)
+ AND
+ "status" = 'Active'
+ """,
+ }, dict(today=today(), condition_column=condition_column), as_dict=1)
+
+ grouped_employees = defaultdict(lambda: [])
+
+ for employee_doc in employees_born_today:
+ grouped_employees[employee_doc.get('company')].append(employee_doc)
+
+ return grouped_employees
+
+
+# --------------------------
+# WORK ANNIVERSARY REMINDERS
+# --------------------------
+def send_work_anniversary_reminders():
+ """Send Employee Work Anniversary Reminders if 'Send Work Anniversary Reminders' is checked"""
+ to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders") or 1)
+ if not to_send:
+ return
+
+ employees_joined_today = get_employees_having_an_event_today("work_anniversary")
+
+ for company, anniversary_persons in employees_joined_today.items():
+ employee_emails = get_all_employee_emails(company)
+ anniversary_person_emails = [get_employee_email(doc) for doc in anniversary_persons]
+ recipients = list(set(employee_emails) - set(anniversary_person_emails))
+
+ reminder_text, message = get_work_anniversary_reminder_text_and_message(anniversary_persons)
+ send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message)
+
+ if len(anniversary_persons) > 1:
+ # email for people sharing work anniversaries
+ for person in anniversary_persons:
+ person_email = person["user_id"] or person["personal_email"] or person["company_email"]
+ others = [d for d in anniversary_persons if d != person]
+ reminder_text, message = get_work_anniversary_reminder_text_and_message(others)
+ send_work_anniversary_reminder(person_email, reminder_text, others, message)
+
+def get_work_anniversary_reminder_text_and_message(anniversary_persons):
+ if len(anniversary_persons) == 1:
+ anniversary_person = anniversary_persons[0]['name']
+ persons_name = anniversary_person
+ # Number of years completed at the company
+ completed_years = getdate().year - anniversary_persons[0]['date_of_joining'].year
+ anniversary_person += f" completed {completed_years} years"
+ else:
+ person_names_with_years = []
+ names = []
+ for person in anniversary_persons:
+ person_text = person['name']
+ names.append(person_text)
+ # Number of years completed at the company
+ completed_years = getdate().year - person['date_of_joining'].year
+ person_text += f" completed {completed_years} years"
+ person_names_with_years.append(person_text)
+
+ # converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
+ anniversary_person = comma_sep(person_names_with_years, frappe._("{0} & {1}"), False)
+ persons_name = comma_sep(names, frappe._("{0} & {1}"), False)
+
+ reminder_text = _("Today {0} at our Company! 🎉").format(anniversary_person)
+ message = _("A friendly reminder of an important date for our team.")
+ message += " "
+ message += _("Everyone, let’s congratulate {0} on their work anniversary!").format(persons_name)
+
+ return reminder_text, message
+
+def send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message):
+ frappe.sendmail(
+ recipients=recipients,
+ subject=_("Work Anniversary Reminder"),
+ template="anniversary_reminder",
+ args=dict(
+ reminder_text=reminder_text,
+ anniversary_persons=anniversary_persons,
+ message=message,
+ ),
+ header=_("🎊️🎊️ Work Anniversary Reminder 🎊️🎊️")
+ )
diff --git a/erpnext/hr/doctype/employee/employee_tree.js b/erpnext/hr/doctype/employee/employee_tree.js
index 9ab091a1eb6..7d6a70013d4 100644
--- a/erpnext/hr/doctype/employee/employee_tree.js
+++ b/erpnext/hr/doctype/employee/employee_tree.js
@@ -33,4 +33,4 @@ frappe.treeview_settings['Employee'] = {
condition: 'frappe.boot.user.can_create.indexOf("Employee") !== -1'
}
],
-};
\ No newline at end of file
+};
diff --git a/erpnext/hr/doctype/employee/test_employee.js b/erpnext/hr/doctype/employee/test_employee.js
index 200dcd79666..3a414584804 100644
--- a/erpnext/hr/doctype/employee/test_employee.js
+++ b/erpnext/hr/doctype/employee/test_employee.js
@@ -37,4 +37,4 @@ QUnit.test("Test: Employee [HR]", function (assert) {
() => frappe.timeout(10),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 8fc7cf19343..5feb6de8f2b 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -1,7 +1,5 @@
# 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
import erpnext
@@ -12,29 +10,6 @@ from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError
test_records = frappe.get_test_records('Employee')
class TestEmployee(unittest.TestCase):
- def test_birthday_reminders(self):
- employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
- employee.date_of_birth = "1992" + frappe.utils.nowdate()[4:]
- employee.company_email = "test@example.com"
- employee.company = "_Test Company"
- employee.save()
-
- from erpnext.hr.doctype.employee.employee import get_employees_who_are_born_today, send_birthday_reminders
-
- employees_born_today = get_employees_who_are_born_today()
- self.assertTrue(employees_born_today.get("_Test Company"))
-
- frappe.db.sql("delete from `tabEmail Queue`")
-
- hr_settings = frappe.get_doc("HR Settings", "HR Settings")
- hr_settings.stop_birthday_reminders = 0
- hr_settings.save()
-
- send_birthday_reminders()
-
- email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
- self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message)
-
def test_employee_status_left(self):
employee1 = make_employee("test_employee_1@company.com")
employee2 = make_employee("test_employee_2@company.com")
diff --git a/erpnext/hr/doctype/employee/test_employee_reminders.py b/erpnext/hr/doctype/employee/test_employee_reminders.py
new file mode 100644
index 00000000000..7e560f512d1
--- /dev/null
+++ b/erpnext/hr/doctype/employee/test_employee_reminders.py
@@ -0,0 +1,173 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+import unittest
+
+from frappe.utils import getdate
+from datetime import timedelta
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.hr_settings.hr_settings import set_proceed_with_frequency_change
+
+
+class TestEmployeeReminders(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ from erpnext.hr.doctype.holiday_list.test_holiday_list import make_holiday_list
+
+ # Create a test holiday list
+ test_holiday_dates = cls.get_test_holiday_dates()
+ test_holiday_list = make_holiday_list(
+ 'TestHolidayRemindersList',
+ holiday_dates=[
+ {'holiday_date': test_holiday_dates[0], 'description': 'test holiday1'},
+ {'holiday_date': test_holiday_dates[1], 'description': 'test holiday2'},
+ {'holiday_date': test_holiday_dates[2], 'description': 'test holiday3', 'weekly_off': 1},
+ {'holiday_date': test_holiday_dates[3], 'description': 'test holiday4'},
+ {'holiday_date': test_holiday_dates[4], 'description': 'test holiday5'},
+ {'holiday_date': test_holiday_dates[5], 'description': 'test holiday6'},
+ ],
+ from_date=getdate()-timedelta(days=10),
+ to_date=getdate()+timedelta(weeks=5)
+ )
+
+ # Create a test employee
+ test_employee = frappe.get_doc(
+ 'Employee',
+ make_employee('test@gopher.io', company="_Test Company")
+ )
+
+ # Attach the holiday list to employee
+ test_employee.holiday_list = test_holiday_list.name
+ test_employee.save()
+
+ # Attach to class
+ cls.test_employee = test_employee
+ cls.test_holiday_dates = test_holiday_dates
+
+ @classmethod
+ def get_test_holiday_dates(cls):
+ today_date = getdate()
+ return [
+ today_date,
+ today_date-timedelta(days=4),
+ today_date-timedelta(days=3),
+ today_date+timedelta(days=1),
+ today_date+timedelta(days=3),
+ today_date+timedelta(weeks=3)
+ ]
+
+ def setUp(self):
+ # Clear Email Queue
+ frappe.db.sql("delete from `tabEmail Queue`")
+
+ def test_is_holiday(self):
+ from erpnext.hr.doctype.employee.employee import is_holiday
+
+ self.assertTrue(is_holiday(self.test_employee.name))
+ self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[1]))
+ self.assertFalse(is_holiday(self.test_employee.name, date=getdate()-timedelta(days=1)))
+
+ # Test weekly_off holidays
+ self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[2]))
+ self.assertFalse(is_holiday(self.test_employee.name, date=self.test_holiday_dates[2], only_non_weekly=True))
+
+ # Test with descriptions
+ has_holiday, descriptions = is_holiday(self.test_employee.name, with_description=True)
+ self.assertTrue(has_holiday)
+ self.assertTrue('test holiday1' in descriptions)
+
+ def test_birthday_reminders(self):
+ employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
+ employee.date_of_birth = "1992" + frappe.utils.nowdate()[4:]
+ employee.company_email = "test@example.com"
+ employee.company = "_Test Company"
+ employee.save()
+
+ from erpnext.hr.doctype.employee.employee_reminders import get_employees_who_are_born_today, send_birthday_reminders
+
+ employees_born_today = get_employees_who_are_born_today()
+ self.assertTrue(employees_born_today.get("_Test Company"))
+
+ hr_settings = frappe.get_doc("HR Settings", "HR Settings")
+ hr_settings.send_birthday_reminders = 1
+ hr_settings.save()
+
+ send_birthday_reminders()
+
+ email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
+ self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message)
+
+ def test_work_anniversary_reminders(self):
+ employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
+ employee.date_of_joining = "1998" + frappe.utils.nowdate()[4:]
+ employee.company_email = "test@example.com"
+ employee.company = "_Test Company"
+ employee.save()
+
+ from erpnext.hr.doctype.employee.employee_reminders import get_employees_having_an_event_today, send_work_anniversary_reminders
+
+ employees_having_work_anniversary = get_employees_having_an_event_today('work_anniversary')
+ self.assertTrue(employees_having_work_anniversary.get("_Test Company"))
+
+ hr_settings = frappe.get_doc("HR Settings", "HR Settings")
+ hr_settings.send_work_anniversary_reminders = 1
+ hr_settings.save()
+
+ send_work_anniversary_reminders()
+
+ email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
+ self.assertTrue("Subject: Work Anniversary Reminder" in email_queue[0].message)
+
+ def test_send_holidays_reminder_in_advance(self):
+ from erpnext.hr.utils import get_holidays_for_employee
+ from erpnext.hr.doctype.employee.employee_reminders import send_holidays_reminder_in_advance
+
+ # Get HR settings and enable advance holiday reminders
+ hr_settings = frappe.get_doc("HR Settings", "HR Settings")
+ hr_settings.send_holiday_reminders = 1
+ set_proceed_with_frequency_change()
+ hr_settings.frequency = 'Weekly'
+ hr_settings.save()
+
+ holidays = get_holidays_for_employee(
+ self.test_employee.get('name'),
+ getdate(), getdate() + timedelta(days=3),
+ only_non_weekly=True,
+ raise_exception=False
+ )
+
+ send_holidays_reminder_in_advance(
+ self.test_employee.get('name'),
+ holidays
+ )
+
+ email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
+ self.assertEqual(len(email_queue), 1)
+
+ def test_advance_holiday_reminders_monthly(self):
+ from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_monthly
+ # Get HR settings and enable advance holiday reminders
+ hr_settings = frappe.get_doc("HR Settings", "HR Settings")
+ hr_settings.send_holiday_reminders = 1
+ set_proceed_with_frequency_change()
+ hr_settings.frequency = 'Monthly'
+ hr_settings.save()
+
+ send_reminders_in_advance_monthly()
+
+ email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
+ self.assertTrue(len(email_queue) > 0)
+
+ def test_advance_holiday_reminders_weekly(self):
+ from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_weekly
+ # Get HR settings and enable advance holiday reminders
+ hr_settings = frappe.get_doc("HR Settings", "HR Settings")
+ hr_settings.send_holiday_reminders = 1
+ hr_settings.frequency = 'Weekly'
+ hr_settings.save()
+
+ send_reminders_in_advance_weekly()
+
+ email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
+ self.assertTrue(len(email_queue) > 0)
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index c88b2b8e49e..100968bb7aa 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -48,4 +48,4 @@ def make_employee_advance(employee_name):
doc.insert()
doc.submit()
- return doc
\ No newline at end of file
+ return doc
diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css
index d25fb2247ed..c8d6644b2f8 100644
--- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css
+++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.css
@@ -18,4 +18,4 @@
.checkbox{
margin-top: -3px;
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js
index 3205a92b1b6..2de5eb0d64e 100644
--- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js
+++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js
@@ -267,5 +267,3 @@ erpnext.EmployeeSelector = Class.extend({
mark_employee_toolbar.appendTo($(this.wrapper));
}
});
-
-
diff --git a/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js b/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js
index 2827d4ba289..48d4344df22 100644
--- a/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js
+++ b/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js
@@ -58,4 +58,4 @@ QUnit.test("Test: Employee attendance tool [HR]", function (assert) {
},
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
index 60ea0f9895d..6c0cd4f963b 100644
--- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
@@ -176,4 +176,3 @@ def time_diff_in_hours(start, end):
def find_index_in_dict(dict_list, key, value):
return next((index for (index, d) in enumerate(dict_list) if d[key] == value), None)
-
diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
index 9f12ef24e62..7ba511f08d5 100644
--- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
@@ -42,11 +42,11 @@ class TestEmployeeCheckin(unittest.TestCase):
self.assertEqual(logs_count, 4)
attendance_count = frappe.db.count('Attendance', {'status':'Present', 'working_hours':8.2,
'employee':employee, 'attendance_date':now_date})
- self.assertEqual(attendance_count, 1)
+ self.assertEqual(attendance_count, 1)
def test_calculate_working_hours(self):
check_in_out_type = ['Alternating entries as IN and OUT during the same shift',
- 'Strictly based on Log Type in Employee Checkin']
+ 'Strictly based on Log Type in Employee Checkin']
working_hours_calc_type = ['First Check-in and Last Check-out',
'Every Valid Check-in and Check-out']
logs_type_1 = [
diff --git a/erpnext/hr/doctype/employee_education/employee_education.py b/erpnext/hr/doctype/employee_education/employee_education.py
index a1d449291c9..f0a76172b2c 100644
--- a/erpnext/hr/doctype/employee_education/employee_education.py
+++ b/erpnext/hr/doctype/employee_education/employee_education.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class EmployeeEducation(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py b/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py
index c7166309f37..517ef57be85 100644
--- a/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py
+++ b/erpnext/hr/doctype/employee_external_work_history/employee_external_work_history.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class EmployeeExternalWorkHistory(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py b/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py
index f2656e9a2b2..df679104185 100644
--- a/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py
+++ b/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py
@@ -10,4 +10,4 @@ def get_data():
'items': ['Employee Onboarding Template', 'Employee Separation Template']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.py b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
index 503b5ea4449..17055829efb 100644
--- a/erpnext/hr/doctype/employee_grievance/employee_grievance.py
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
@@ -12,4 +12,3 @@ class EmployeeGrievance(Document):
bold("Invalid"),
bold("Resolved"))
)
-
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js
index fc08e216099..11672ca4e0e 100644
--- a/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance_list.js
@@ -9,4 +9,4 @@ frappe.listview_settings["Employee Grievance"] = {
};
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
index a615b20a5a2..ed897ee1032 100644
--- a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
+++ b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
@@ -48,4 +48,3 @@ def create_grievance_type():
grievance_type.save()
return grievance_type.name
-
diff --git a/erpnext/hr/doctype/employee_group/test_employee_group.py b/erpnext/hr/doctype/employee_group/test_employee_group.py
index 3a6bf8594b3..26a61c407b2 100644
--- a/erpnext/hr/doctype/employee_group/test_employee_group.py
+++ b/erpnext/hr/doctype/employee_group/test_employee_group.py
@@ -29,4 +29,4 @@ def make_employee_group():
def get_employee_group():
employee_group = frappe.db.exists("Employee Group", "_Test Employee Group")
- return employee_group
\ No newline at end of file
+ return employee_group
diff --git a/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py b/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py
index d0f3d8d016a..2f385a8113e 100644
--- a/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py
+++ b/erpnext/hr/doctype/employee_internal_work_history/employee_internal_work_history.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class EmployeeInternalWorkHistory(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
index 6cc2bf5cd85..0cb50475bf8 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
@@ -57,4 +57,3 @@ def make_employee(source_name, target_doc=None):
}}
}, target_doc, set_missing_values)
return doc
-
diff --git a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py
index 837da530162..ab0eb2f5dce 100644
--- a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py
+++ b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Employee Onboarding']
},
],
- }
\ 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
index 0493306166f..547a95e3bdf 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral.py
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.py
@@ -70,4 +70,3 @@ def create_additional_salary(doc):
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
index afa2a1ff1fc..caca2961a1a 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
+++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
@@ -12,4 +12,4 @@ def get_data():
},
]
- }
\ 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
index 7533ab635f5..38dfc4d4c86 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral_list.js
+++ b/erpnext/hr/doctype/employee_referral/employee_referral_list.js
@@ -11,4 +11,4 @@ frappe.listview_settings['Employee Referral'] = {
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
index a674f390265..599f3262240 100644
--- a/erpnext/hr/doctype/employee_referral/test_employee_referral.py
+++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
@@ -57,4 +57,4 @@ def create_employee_referral():
emp_ref.save()
emp_ref.submit()
- return emp_ref
\ No newline at end of file
+ return emp_ref
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
index 713fcf526b5..0b72efa1378 100644
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
@@ -23,4 +23,4 @@ class TestEmployeeSeparation(unittest.TestCase):
separation.submit()
self.assertEqual(separation.docstatus, 1)
separation.cancel()
- self.assertEqual(separation.project, "")
\ No newline at end of file
+ self.assertEqual(separation.project, "")
diff --git a/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py b/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py
index 39345f07663..75f985cec39 100644
--- a/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py
+++ b/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Employee Separation']
},
],
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/employment_type/employment_type.py b/erpnext/hr/doctype/employment_type/employment_type.py
index fb306b65d28..00aa6bb9bc4 100644
--- a/erpnext/hr/doctype/employment_type/employment_type.py
+++ b/erpnext/hr/doctype/employment_type/employment_type.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class EmploymentType(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/employment_type/test_employment_type.js b/erpnext/hr/doctype/employment_type/test_employment_type.js
index 9835aabd481..fd7c6a1ce33 100644
--- a/erpnext/hr/doctype/employment_type/test_employment_type.js
+++ b/erpnext/hr/doctype/employment_type/test_employment_type.js
@@ -19,4 +19,4 @@ QUnit.test("Test: Employment type [HR]", function (assert) {
'name of employment type correctly saved'),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/employment_type/test_employment_type.py b/erpnext/hr/doctype/employment_type/test_employment_type.py
index e138136605c..0297ffa01a3 100644
--- a/erpnext/hr/doctype/employment_type/test_employment_type.py
+++ b/erpnext/hr/doctype/employment_type/test_employment_type.py
@@ -4,4 +4,4 @@ from __future__ import unicode_literals
import frappe
-test_records = frappe.get_test_records('Employment Type')
\ No newline at end of file
+test_records = frappe.get_test_records('Employment Type')
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index 629341ff2a5..3c4c672816c 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -442,4 +442,4 @@ frappe.ui.form.on("Expense Taxes and Charges", {
tax_amount: function(frm, cdt, cdn) {
frm.trigger("calculate_total_tax", cdt, cdn);
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py b/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py
index 7de8f4fc13a..fe973507019 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py
@@ -17,4 +17,4 @@ def get_data():
'items': ['Employee Advance']
},
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.js b/erpnext/hr/doctype/expense_claim/test_expense_claim.js
index d0c43d3be47..2529faec983 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.js
@@ -42,4 +42,3 @@ QUnit.test("Test: Expense Claim [HR]", function (assert) {
() => done()
]);
});
-
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 96ea686706c..c2bd1e9f9f1 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -72,7 +72,7 @@ class TestExpenseClaim(unittest.TestCase):
def test_expense_claim_gl_entry(self):
payable_account = get_payable_account(company_name)
taxes = generate_taxes()
- expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4",
+ expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4",
do_not_submit=True, taxes=taxes)
expense_claim.submit()
diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py
index 8bfa1ade072..5d48990c5ce 100644
--- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py
+++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class ExpenseClaimDetail(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py
index 2595506486d..a637a540213 100644
--- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py
+++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py
@@ -25,4 +25,4 @@ class ExpenseClaimType(Document):
"""Error when Company of Ledger account doesn't match with Company Selected"""
if frappe.db.get_value("Account", entry.default_account, "company") != entry.company:
frappe.throw(_("Account {0} does not match with Company {1}"
- ).format(entry.default_account, entry.company))
\ No newline at end of file
+ ).format(entry.default_account, entry.company))
diff --git a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js
index 62234e08a04..3c9ed35313d 100644
--- a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js
+++ b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js
@@ -27,4 +27,3 @@ QUnit.test("Test: Expense Claim Type [HR]", function (assert) {
() => done()
]);
});
-
diff --git a/erpnext/hr/doctype/holiday/holiday.py b/erpnext/hr/doctype/holiday/holiday.py
index aabab0b0d35..78a95b9b741 100644
--- a/erpnext/hr/doctype/holiday/holiday.py
+++ b/erpnext/hr/doctype/holiday/holiday.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class Holiday(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py b/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py
index 22e1de0c342..05641c7dc26 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py
@@ -18,4 +18,4 @@ def get_data():
'items': ['Service Level', 'Service Level Agreement']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/holiday_list/test_holiday_list.js b/erpnext/hr/doctype/holiday_list/test_holiday_list.js
index bfcafa9460c..ce766143a62 100644
--- a/erpnext/hr/doctype/holiday_list/test_holiday_list.js
+++ b/erpnext/hr/doctype/holiday_list/test_holiday_list.js
@@ -39,4 +39,4 @@ QUnit.test("Test: Holiday list [HR]", function (assert) {
},
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.js b/erpnext/hr/doctype/hr_settings/hr_settings.js
index fd082fda09b..ec99472d9bc 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.js
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.js
@@ -5,4 +5,4 @@ frappe.ui.form.on('HR Settings', {
restrict_backdated_leave_application: function(frm) {
frm.toggle_reqd("role_allowed_to_create_backdated_leave_application", frm.doc.restrict_backdated_leave_application);
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index 2396a8eee92..8aa3c0ca9f1 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -11,8 +11,14 @@
"emp_created_by",
"column_break_4",
"standard_working_hours",
- "stop_birthday_reminders",
"expense_approver_mandatory_in_expense_claim",
+ "reminders_section",
+ "send_birthday_reminders",
+ "column_break_9",
+ "send_work_anniversary_reminders",
+ "column_break_11",
+ "send_holiday_reminders",
+ "frequency",
"leave_settings",
"send_leave_notification",
"leave_approval_notification_template",
@@ -50,13 +56,6 @@
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
- {
- "default": "0",
- "description": "Don't send employee birthday reminders",
- "fieldname": "stop_birthday_reminders",
- "fieldtype": "Check",
- "label": "Stop Birthday Reminders"
- },
{
"default": "1",
"fieldname": "expense_approver_mandatory_in_expense_claim",
@@ -142,13 +141,53 @@
"fieldname": "standard_working_hours",
"fieldtype": "Int",
"label": "Standard Working Hours"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "reminders_section",
+ "fieldtype": "Section Break",
+ "label": "Reminders"
+ },
+ {
+ "default": "1",
+ "fieldname": "send_holiday_reminders",
+ "fieldtype": "Check",
+ "label": "Holidays"
+ },
+ {
+ "default": "1",
+ "fieldname": "send_work_anniversary_reminders",
+ "fieldtype": "Check",
+ "label": "Work Anniversaries "
+ },
+ {
+ "default": "Weekly",
+ "depends_on": "eval:doc.send_holiday_reminders",
+ "fieldname": "frequency",
+ "fieldtype": "Select",
+ "label": "Set the frequency for holiday reminders",
+ "options": "Weekly\nMonthly"
+ },
+ {
+ "default": "1",
+ "fieldname": "send_birthday_reminders",
+ "fieldtype": "Check",
+ "label": "Birthdays"
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
- "modified": "2021-05-11 10:52:56.192773",
+ "modified": "2021-08-24 14:54:12.834162",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.py b/erpnext/hr/doctype/hr_settings/hr_settings.py
index ced98fb9a58..a47409363c7 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.py
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.py
@@ -1,18 +1,79 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
# For license information, please see license.txt
-from __future__ import unicode_literals
import frappe
+
from frappe.model.document import Document
+from frappe.utils import format_date
+
+# Wether to proceed with frequency change
+PROCEED_WITH_FREQUENCY_CHANGE = False
class HRSettings(Document):
def validate(self):
self.set_naming_series()
+ # Based on proceed flag
+ global PROCEED_WITH_FREQUENCY_CHANGE
+ if not PROCEED_WITH_FREQUENCY_CHANGE:
+ self.validate_frequency_change()
+ PROCEED_WITH_FREQUENCY_CHANGE = False
+
def set_naming_series(self):
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
set_by_naming_series("Employee", "employee_number",
self.get("emp_created_by")=="Naming Series", hide_name_field=True)
+ def validate_frequency_change(self):
+ weekly_job, monthly_job = None, None
+
+ try:
+ weekly_job = frappe.get_doc(
+ 'Scheduled Job Type',
+ 'employee_reminders.send_reminders_in_advance_weekly'
+ )
+
+ monthly_job = frappe.get_doc(
+ 'Scheduled Job Type',
+ 'employee_reminders.send_reminders_in_advance_monthly'
+ )
+ except frappe.DoesNotExistError:
+ return
+
+ next_weekly_trigger = weekly_job.get_next_execution()
+ next_monthly_trigger = monthly_job.get_next_execution()
+
+ if self.freq_changed_from_monthly_to_weekly():
+ if next_monthly_trigger < next_weekly_trigger:
+ self.show_freq_change_warning(next_monthly_trigger, next_weekly_trigger)
+
+ elif self.freq_changed_from_weekly_to_monthly():
+ if next_monthly_trigger > next_weekly_trigger:
+ self.show_freq_change_warning(next_weekly_trigger, next_monthly_trigger)
+
+ def freq_changed_from_weekly_to_monthly(self):
+ return self.has_value_changed("frequency") and self.frequency == "Monthly"
+
+ def freq_changed_from_monthly_to_weekly(self):
+ return self.has_value_changed("frequency") and self.frequency == "Weekly"
+
+ def show_freq_change_warning(self, from_date, to_date):
+ from_date = frappe.bold(format_date(from_date))
+ to_date = frappe.bold(format_date(to_date))
+ frappe.msgprint(
+ msg=frappe._('Employees will miss holiday reminders from {} until {}. Do you want to proceed with this change?').format(from_date, to_date),
+ title='Confirm change in Frequency',
+ primary_action={
+ 'label': frappe._('Yes, Proceed'),
+ 'client_action': 'erpnext.proceed_save_with_reminders_frequency_change'
+ },
+ raise_exception=frappe.ValidationError
+ )
+
+@frappe.whitelist()
+def set_proceed_with_frequency_change():
+ '''Enables proceed with frequency change'''
+ global PROCEED_WITH_FREQUENCY_CHANGE
+ PROCEED_WITH_FREQUENCY_CHANGE = True
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.js b/erpnext/hr/doctype/job_applicant/job_applicant.js
index c62515597ce..7658bc93539 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.js
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.js
@@ -38,4 +38,4 @@ frappe.ui.form.on("Job Applicant", {
});
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index 0594ba395ba..14aeb03a87e 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -50,4 +50,3 @@ class JobApplicant(Document):
if names:
frappe.throw(_("Email Address must be unique, already exists for {0}").format(comma_and(names)), frappe.DuplicateEntryError)
-
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py
index 7f131151e18..ed97978a8ad 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py
@@ -12,4 +12,4 @@ def get_data():
'items': ['Job Offer']
},
],
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.js b/erpnext/hr/doctype/job_applicant/test_job_applicant.js
index b5391c8bf36..741a182addc 100644
--- a/erpnext/hr/doctype/job_applicant/test_job_applicant.js
+++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.js
@@ -26,4 +26,3 @@ QUnit.test("Test: Job Opening [HR]", function (assert) {
() => done()
]);
});
-
diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.js b/erpnext/hr/doctype/job_offer/test_job_offer.js
index c9d7d2bef79..5339b9c3d67 100644
--- a/erpnext/hr/doctype/job_offer/test_job_offer.js
+++ b/erpnext/hr/doctype/job_offer/test_job_offer.js
@@ -48,4 +48,4 @@ QUnit.test("Test: Job Offer [HR]", function (assert) {
() => frappe.timeout(2),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py
index b3e1dc8d87b..edb21321fcc 100644
--- a/erpnext/hr/doctype/job_offer/test_job_offer.py
+++ b/erpnext/hr/doctype/job_offer/test_job_offer.py
@@ -79,4 +79,4 @@ def create_staffing_plan(**args):
})
staffing_plan.insert()
staffing_plan.submit()
- return staffing_plan
\ No newline at end of file
+ return staffing_plan
diff --git a/erpnext/hr/doctype/job_opening/job_opening_dashboard.py b/erpnext/hr/doctype/job_opening/job_opening_dashboard.py
index c0890b4f57c..31ef33ef2ce 100644
--- a/erpnext/hr/doctype/job_opening/job_opening_dashboard.py
+++ b/erpnext/hr/doctype/job_opening/job_opening_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Job Applicant']
}
],
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/job_opening/templates/job_opening_row.html b/erpnext/hr/doctype/job_opening/templates/job_opening_row.html
index c015101600a..69bf49bef77 100644
--- a/erpnext/hr/doctype/job_opening/templates/job_opening_row.html
+++ b/erpnext/hr/doctype/job_opening/templates/job_opening_row.html
@@ -1,16 +1,16 @@
{{ doc.job_title }}
{{ doc.description }}
- {%- if doc.publish_salary_range -%}
+ {%- if doc.publish_salary_range -%}
{{_("Salary range per month")}}: {{ frappe.format_value(frappe.utils.flt(doc.lower_range), currency=doc.currency) }} - {{ frappe.format_value(frappe.utils.flt(doc.upper_range), currency=doc.currency) }}
{% endif %}
{%- if doc.job_application_route -%}
-
{{ _("Apply Now") }}
{% else %}
-
{{ _("Apply Now") }}
{% endif %}
diff --git a/erpnext/hr/doctype/job_opening/test_job_opening.js b/erpnext/hr/doctype/job_opening/test_job_opening.js
index b9e6c0a8b2d..cc2f027e85b 100644
--- a/erpnext/hr/doctype/job_opening/test_job_opening.js
+++ b/erpnext/hr/doctype/job_opening/test_job_opening.js
@@ -24,4 +24,3 @@ QUnit.test("Test: Job Opening [HR]", function (assert) {
() => done()
]);
});
-
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
index e9e129cdd24..d94764104d0 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
@@ -100,4 +100,4 @@ frappe.ui.form.on("Leave Allocation", {
frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated));
}
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py
index 7456aebb457..7a063d92eac 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py
@@ -17,4 +17,4 @@ def get_data():
'items': ['Employee Leave Balance']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js
index 0ef78f2f883..d5364fc8b2e 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js
@@ -38,4 +38,4 @@ QUnit.test("Test: Leave allocation [HR]", function (assert) {
"total leave calculation is correctly set"),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/leave_application/leave_application_calendar.js b/erpnext/hr/doctype/leave_application/leave_application_calendar.js
index 0286f300646..31faadb1079 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_calendar.js
+++ b/erpnext/hr/doctype/leave_application/leave_application_calendar.js
@@ -17,4 +17,4 @@ frappe.views.calendar["Leave Application"] = {
}
},
get_events_method: "erpnext.hr.doctype.leave_application.leave_application.get_events"
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
index c1d6a6665b6..c45717f5870 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
+++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
@@ -17,4 +17,4 @@ def get_data():
'items': ['Employee Leave Balance']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/leave_application/leave_application_email_template.html b/erpnext/hr/doctype/leave_application/leave_application_email_template.html
index 209302e8f30..14ca41bebca 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_email_template.html
+++ b/erpnext/hr/doctype/leave_application/leave_application_email_template.html
@@ -21,5 +21,5 @@
Status
{{status}}
-
+
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.js b/erpnext/hr/doctype/leave_application/test_leave_application.js
index 6d7b6a70588..0866b0b6d2a 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.js
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.js
@@ -39,4 +39,4 @@ QUnit.test("Test: Leave application [HR]", function (assert) {
// "leave for correct employee is submitted"),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py b/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py
index 2aa54984ec5..45aa4915bc6 100644
--- a/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py
+++ b/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py
@@ -8,4 +8,4 @@ def get_data():
'items': ['Department']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.js b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.js
index 453787865c7..b39601b490d 100644
--- a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.js
+++ b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.js
@@ -24,4 +24,4 @@ QUnit.test("Test: Leave block list [HR]", function (assert) {
'name of blocked leave list correctly saved'),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py b/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py
index be06b768bf7..8e5a09e01ec 100644
--- a/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py
+++ b/erpnext/hr/doctype/leave_block_list_allow/leave_block_list_allow.py
@@ -9,4 +9,4 @@ import frappe
from frappe.model.document import Document
class LeaveBlockListAllow(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py b/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py
index f4028f54eba..54978a1e83a 100644
--- a/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py
+++ b/erpnext/hr/doctype/leave_block_list_date/leave_block_list_date.py
@@ -9,4 +9,4 @@ import frappe
from frappe.model.document import Document
class LeaveBlockListDate(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js
index b60e225a727..4a450807ccf 100644
--- a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js
+++ b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js
@@ -21,4 +21,4 @@ frappe.ui.form.on("Leave Control Panel", {
});
}
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js b/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js
index 2b5cec1c1e1..9d373277175 100644
--- a/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js
+++ b/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js
@@ -47,4 +47,4 @@ QUnit.test("Test: Leave control panel [HR]", function (assert) {
},
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index 912bd8ad92f..d136210a043 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -134,4 +134,4 @@ def create_leave_encashment(leave_allocation):
leave_type=allocation.leave_type,
encashment_date=allocation.to_date
))
- leave_encashment.insert(ignore_permissions=True)
\ No newline at end of file
+ leave_encashment.insert(ignore_permissions=True)
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
index cf130361810..33a6243e609 100644
--- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
@@ -185,4 +185,4 @@ def expire_carried_forward_allocation(allocation):
from_date=allocation.to_date,
to_date=allocation.to_date
)
- create_leave_ledger_entry(allocation, args)
\ No newline at end of file
+ create_leave_ledger_entry(allocation, args)
diff --git a/erpnext/hr/doctype/leave_period/leave_period_dashboard.py b/erpnext/hr/doctype/leave_period/leave_period_dashboard.py
index 1572de3cb72..7c2c9632d85 100644
--- a/erpnext/hr/doctype/leave_period/leave_period_dashboard.py
+++ b/erpnext/hr/doctype/leave_period/leave_period_dashboard.py
@@ -10,4 +10,4 @@ def get_data():
'items': ['Leave Allocation']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py
index b5857bcd8fe..cbb34371fc9 100644
--- a/erpnext/hr/doctype/leave_period/test_leave_period.py
+++ b/erpnext/hr/doctype/leave_period/test_leave_period.py
@@ -27,4 +27,4 @@ def create_leave_period(from_date, to_date, company=None):
"to_date": to_date,
"is_active": 1
}).insert()
- return leave_period
\ No newline at end of file
+ return leave_period
diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
index ff7f0422e03..474f3a77ad0 100644
--- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
+++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
@@ -10,4 +10,4 @@ def get_data():
'items': ['Leave Policy Assignment', 'Leave Allocation']
},
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
index fc868ea15a6..af7567b5bc7 100644
--- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py
+++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
@@ -28,4 +28,4 @@ def create_leave_policy(**args):
"leave_type": args.leave_type or "_Test Leave Type",
"annual_allocation": args.annual_allocation or 10
}]
- })
\ No newline at end of file
+ })
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
index 4bb0535cf8c..a2f7f5866b7 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
@@ -10,4 +10,4 @@ def get_data():
'items': ['Leave Allocation']
},
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
index 8fe4b8f8efa..8b954c46a10 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
@@ -105,4 +105,4 @@ frappe.listview_settings['Leave Policy Assignment'] = {
});
}
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
index 9a14e3588d0..0089804f512 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -99,5 +99,3 @@ class TestLeavePolicyAssignment(unittest.TestCase):
def tearDown(self):
for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec
-
-
diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json
index fc577ef1d3d..8f2ae6eb15d 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.json
+++ b/erpnext/hr/doctype/leave_type/leave_type.json
@@ -214,7 +214,7 @@
"icon": "fa fa-flag",
"idx": 1,
"links": [],
- "modified": "2021-03-02 11:22:33.776320",
+ "modified": "2021-08-12 16:10:36.464690",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Type",
@@ -248,5 +248,6 @@
}
],
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_type/leave_type_dashboard.py b/erpnext/hr/doctype/leave_type/leave_type_dashboard.py
index 5cae9a8809c..c8944fcb9e2 100644
--- a/erpnext/hr/doctype/leave_type/leave_type_dashboard.py
+++ b/erpnext/hr/doctype/leave_type/leave_type_dashboard.py
@@ -11,4 +11,4 @@ def get_data():
'items': ['Attendance', 'Leave Encashment']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.js b/erpnext/hr/doctype/leave_type/test_leave_type.js
index d939a248102..db910cde512 100644
--- a/erpnext/hr/doctype/leave_type/test_leave_type.js
+++ b/erpnext/hr/doctype/leave_type/test_leave_type.js
@@ -19,4 +19,4 @@ QUnit.test("Test: Leave type [HR]", function (assert) {
'leave type correctly saved'),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py
index 7fef2975c8a..048dddd3ef9 100644
--- a/erpnext/hr/doctype/leave_type/test_leave_type.py
+++ b/erpnext/hr/doctype/leave_type/test_leave_type.py
@@ -28,4 +28,4 @@ def create_leave_type(**args):
if leave_type.is_ppl:
leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5
- return leave_type
\ No newline at end of file
+ return leave_type
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js b/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js
index bb692e1402e..5d2360f10fa 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js
@@ -10,4 +10,4 @@ frappe.views.calendar["Shift Assignment"] = {
"allDay": "allDay",
},
get_events_method: "erpnext.hr.doctype.shift_assignment.shift_assignment.get_events"
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py
index 4c3c1ed579e..07d92fe61d6 100644
--- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py
@@ -77,4 +77,4 @@ class TestShiftAssignment(unittest.TestCase):
"status": 'Active'
})
- self.assertRaises(frappe.ValidationError, shift_assignment_3.save)
\ No newline at end of file
+ self.assertRaises(frappe.ValidationError, shift_assignment_3.save)
diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py
index 6461f07552b..2731da125a8 100644
--- a/erpnext/hr/doctype/shift_request/shift_request.py
+++ b/erpnext/hr/doctype/shift_request/shift_request.py
@@ -94,4 +94,4 @@ class ShiftRequest(Document):
msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee,
d['shift_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \
+ """ {0}""".format(d["name"])
- frappe.throw(msg, OverlapError)
\ No newline at end of file
+ frappe.throw(msg, OverlapError)
diff --git a/erpnext/hr/doctype/shift_request/shift_request_dashboard.py b/erpnext/hr/doctype/shift_request/shift_request_dashboard.py
index e3bf5df9490..f70b61a20a6 100644
--- a/erpnext/hr/doctype/shift_request/shift_request_dashboard.py
+++ b/erpnext/hr/doctype/shift_request/shift_request_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Shift Assignment']
},
],
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py
index 9c0d8e31985..60b7676e251 100644
--- a/erpnext/hr/doctype/shift_request/test_shift_request.py
+++ b/erpnext/hr/doctype/shift_request/test_shift_request.py
@@ -15,24 +15,35 @@ class TestShiftRequest(unittest.TestCase):
for doctype in ["Shift Request", "Shift Assignment"]:
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
+ def tearDown(self):
+ frappe.db.rollback()
+
def test_make_shift_request(self):
+ "Test creation/updation of Shift Assignment from Shift Request."
department = frappe.get_value("Employee", "_T-Employee-00001", 'department')
set_shift_approver(department)
approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
shift_request = make_shift_request(approver)
- shift_assignments = frappe.db.sql('''
- SELECT shift_request, employee
- FROM `tabShift Assignment`
- WHERE shift_request = '{0}'
- '''.format(shift_request.name), as_dict=1)
- for d in shift_assignments:
- employee = d.get('employee')
- self.assertEqual(shift_request.employee, employee)
- shift_request.cancel()
- shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')})
- self.assertEqual(shift_assignment_doc.docstatus, 2)
+ # Only one shift assignment is created against a shift request
+ shift_assignment = frappe.db.get_value(
+ "Shift Assignment",
+ filters={"shift_request": shift_request.name},
+ fieldname=["employee", "docstatus"],
+ as_dict=True
+ )
+ self.assertEqual(shift_request.employee, shift_assignment.employee)
+ self.assertEqual(shift_assignment.docstatus, 1)
+
+ shift_request.cancel()
+
+ shift_assignment_docstatus = frappe.db.get_value(
+ "Shift Assignment",
+ filters={"shift_request": shift_request.name},
+ fieldname="docstatus"
+ )
+ self.assertEqual(shift_assignment_docstatus, 2)
def test_shift_request_approver_perms(self):
employee = frappe.get_doc("Employee", "_T-Employee-00001")
@@ -95,4 +106,4 @@ def make_shift_request(approver, do_not_submit=0):
return shift_request
shift_request.submit()
- return shift_request
\ No newline at end of file
+ return shift_request
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.js b/erpnext/hr/doctype/staffing_plan/staffing_plan.js
index 04af2323c72..597ecceaa0f 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.js
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.js
@@ -103,4 +103,4 @@ var set_total_estimated_budget = function(frm) {
})
frm.set_value('total_estimated_budget', estimated_budget);
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py b/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py
index 35a303f0fb2..8e89d53c8e0 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Job Opening']
}
],
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
index 628255b11f5..1c6218e9a70 100644
--- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
@@ -94,4 +94,4 @@ def make_company():
company.parent_company = "_Test Company 3"
company.default_currency = "INR"
company.country = "Pakistan"
- company.insert()
\ No newline at end of file
+ company.insert()
diff --git a/erpnext/hr/doctype/training_event/test_training_event.py b/erpnext/hr/doctype/training_event/test_training_event.py
index 313f90eba85..6a275b330c0 100644
--- a/erpnext/hr/doctype/training_event/test_training_event.py
+++ b/erpnext/hr/doctype/training_event/test_training_event.py
@@ -11,21 +11,34 @@ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_
class TestTrainingEvent(unittest.TestCase):
def setUp(self):
create_training_program("Basic Training")
- self.employee = make_employee("robert_loan@trainig.com")
- self.employee2 = make_employee("suzie.tan@trainig.com")
+ employee = make_employee("robert_loan@trainig.com")
+ employee2 = make_employee("suzie.tan@trainig.com")
+ self.attendees = [
+ {"employee": employee},
+ {"employee": employee2}
+ ]
+
+ def test_training_event_status_update(self):
+ training_event = create_training_event(self.attendees)
+ training_event.submit()
+
+ training_event.event_status = "Completed"
+ training_event.save()
+ training_event.reload()
+
+ for entry in training_event.employees:
+ self.assertEqual(entry.status, "Completed")
+
+ training_event.event_status = "Scheduled"
+ training_event.save()
+ training_event.reload()
+
+ for entry in training_event.employees:
+ self.assertEqual(entry.status, "Open")
+
+ def tearDown(self):
+ frappe.db.rollback()
- def test_create_training_event(self):
- if not frappe.db.get_value("Training Event", "Basic Training Event"):
- frappe.get_doc({
- "doctype": "Training Event",
- "event_name": "Basic Training Event",
- "training_program": "Basic Training",
- "location": "Union Square",
- "start_time": add_days(today(), 5),
- "end_time": add_days(today(), 6),
- "introduction": "Welcome to the Basic Training Event",
- "employees": get_attendees(self.employee, self.employee2)
- }).insert()
def create_training_program(training_program):
if not frappe.db.get_value("Training Program", training_program):
@@ -35,8 +48,14 @@ def create_training_program(training_program):
"description": training_program
}).insert()
-def get_attendees(employee, employee2):
- return [
- {"employee": employee},
- {"employee": employee2}
- ]
\ No newline at end of file
+def create_training_event(attendees):
+ return frappe.get_doc({
+ "doctype": "Training Event",
+ "event_name": "Basic Training Event",
+ "training_program": "Basic Training",
+ "location": "Union Square",
+ "start_time": add_days(today(), 5),
+ "end_time": add_days(today(), 6),
+ "introduction": "Welcome to the Basic Training Event",
+ "employees": attendees
+ }).insert()
diff --git a/erpnext/hr/doctype/training_event/tests/test_training_event.js b/erpnext/hr/doctype/training_event/tests/test_training_event.js
index 8ff4fecd6ee..08031a1963e 100644
--- a/erpnext/hr/doctype/training_event/tests/test_training_event.js
+++ b/erpnext/hr/doctype/training_event/tests/test_training_event.js
@@ -56,4 +56,4 @@ QUnit.test("Test: Training Event [HR]", function (assert) {
() => frappe.timeout(2),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/training_event/training_event.js b/erpnext/hr/doctype/training_event/training_event.js
index d5f6e5f573e..642e6a1fd75 100644
--- a/erpnext/hr/doctype/training_event/training_event.js
+++ b/erpnext/hr/doctype/training_event/training_event.js
@@ -46,4 +46,3 @@ frappe.ui.form.on("Training Event Employee", {
frm.events.set_employee_query(frm);
}
});
-
diff --git a/erpnext/hr/doctype/training_event/training_event.py b/erpnext/hr/doctype/training_event/training_event.py
index 5064f033081..e2c30cb3145 100644
--- a/erpnext/hr/doctype/training_event/training_event.py
+++ b/erpnext/hr/doctype/training_event/training_event.py
@@ -14,10 +14,25 @@ class TrainingEvent(Document):
self.set_employee_emails()
self.validate_period()
+ def on_update_after_submit(self):
+ self.set_status_for_attendees()
+
def set_employee_emails(self):
self.employee_emails = ', '.join(get_employee_emails([d.employee
for d in self.employees]))
def validate_period(self):
if time_diff_in_seconds(self.end_time, self.start_time) <= 0:
- frappe.throw(_('End time cannot be before start time'))
\ No newline at end of file
+ frappe.throw(_('End time cannot be before start time'))
+
+ def set_status_for_attendees(self):
+ if self.event_status == 'Completed':
+ for employee in self.employees:
+ if employee.attendance == 'Present' and employee.status != 'Feedback Submitted':
+ employee.status = 'Completed'
+
+ elif self.event_status == 'Scheduled':
+ for employee in self.employees:
+ employee.status = 'Open'
+
+ self.db_update_all()
diff --git a/erpnext/hr/doctype/training_event/training_event_dashboard.py b/erpnext/hr/doctype/training_event/training_event_dashboard.py
index 1c1645c766f..19afd8dd6e1 100644
--- a/erpnext/hr/doctype/training_event/training_event_dashboard.py
+++ b/erpnext/hr/doctype/training_event/training_event_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Training Result', 'Training Feedback']
},
],
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/training_feedback/test_training_feedback.js b/erpnext/hr/doctype/training_feedback/test_training_feedback.js
index 9daa51f9275..5c825aea7fb 100644
--- a/erpnext/hr/doctype/training_feedback/test_training_feedback.js
+++ b/erpnext/hr/doctype/training_feedback/test_training_feedback.js
@@ -49,4 +49,3 @@ QUnit.test("Test: Training Feedback [HR]", function (assert) {
() => done()
]);
});
-
diff --git a/erpnext/hr/doctype/training_feedback/test_training_feedback.py b/erpnext/hr/doctype/training_feedback/test_training_feedback.py
index 34559982a24..4c0c18029d0 100644
--- a/erpnext/hr/doctype/training_feedback/test_training_feedback.py
+++ b/erpnext/hr/doctype/training_feedback/test_training_feedback.py
@@ -5,8 +5,63 @@ from __future__ import unicode_literals
import frappe
import unittest
-
-# test_records = frappe.get_test_records('Training Feedback')
-
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee
+from erpnext.hr.doctype.training_event.test_training_event import create_training_program, create_training_event
class TestTrainingFeedback(unittest.TestCase):
- pass
+ def setUp(self):
+ create_training_program("Basic Training")
+ self.employee = make_employee("robert_loan@trainig.com")
+ self.employee2 = make_employee("suzie.tan@trainig.com")
+ self.attendees = [{"employee": self.employee}]
+
+ def test_employee_validations_for_feedback(self):
+ training_event = create_training_event(self.attendees)
+ training_event.submit()
+
+ training_event.event_status = "Completed"
+ training_event.save()
+ training_event.reload()
+
+ # should not allow creating feedback since employee2 was not part of the event
+ feedback = create_training_feedback(training_event.name, self.employee2)
+ self.assertRaises(frappe.ValidationError, feedback.save)
+
+ # cannot record feedback for absent employee
+ employee = frappe.db.get_value("Training Event Employee", {
+ "parent": training_event.name,
+ "employee": self.employee
+ }, "name")
+
+ frappe.db.set_value("Training Event Employee", employee, "attendance", "Absent")
+ feedback = create_training_feedback(training_event.name, self.employee)
+ self.assertRaises(frappe.ValidationError, feedback.save)
+
+ def test_training_feedback_status(self):
+ training_event = create_training_event(self.attendees)
+ training_event.submit()
+
+ training_event.event_status = "Completed"
+ training_event.save()
+ training_event.reload()
+
+ feedback = create_training_feedback(training_event.name, self.employee)
+ feedback.submit()
+
+ status = frappe.db.get_value("Training Event Employee", {
+ "parent": training_event.name,
+ "employee": self.employee
+ }, "status")
+
+ self.assertEqual(status, "Feedback Submitted")
+
+ def tearDown(self):
+ frappe.db.rollback()
+
+
+def create_training_feedback(event, employee):
+ return frappe.get_doc({
+ "doctype": "Training Feedback",
+ "training_event": event,
+ "employee": employee,
+ "feedback": "Test"
+ })
diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.js b/erpnext/hr/doctype/training_feedback/training_feedback.js
index 0dea098a67e..5e875c1b434 100644
--- a/erpnext/hr/doctype/training_feedback/training_feedback.js
+++ b/erpnext/hr/doctype/training_feedback/training_feedback.js
@@ -7,4 +7,4 @@ frappe.ui.form.on('Training Feedback', {
frm.add_fetch("training_event", "event_name", "event_name");
frm.add_fetch("training_event", "trainer_name", "trainer_name");
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.py b/erpnext/hr/doctype/training_feedback/training_feedback.py
index 1a334507917..3d4b9b3ea96 100644
--- a/erpnext/hr/doctype/training_feedback/training_feedback.py
+++ b/erpnext/hr/doctype/training_feedback/training_feedback.py
@@ -11,15 +11,34 @@ class TrainingFeedback(Document):
def validate(self):
training_event = frappe.get_doc("Training Event", self.training_event)
if training_event.docstatus != 1:
- frappe.throw(_('{0} must be submitted').format(_('Training Event')))
+ frappe.throw(_("{0} must be submitted").format(_("Training Event")))
+
+ emp_event_details = frappe.db.get_value("Training Event Employee", {
+ "parent": self.training_event,
+ "employee": self.employee
+ }, ["name", "attendance"], as_dict=True)
+
+ if not emp_event_details:
+ frappe.throw(_("Employee {0} not found in Training Event Participants.").format(
+ frappe.bold(self.employee_name)))
+
+ if emp_event_details.attendance == "Absent":
+ frappe.throw(_("Feedback cannot be recorded for an absent Employee."))
def on_submit(self):
- training_event = frappe.get_doc("Training Event", self.training_event)
- event_status = None
- for e in training_event.employees:
- if e.employee == self.employee:
- event_status = 'Feedback Submitted'
- break
+ employee = frappe.db.get_value("Training Event Employee", {
+ "parent": self.training_event,
+ "employee": self.employee
+ })
- if event_status:
- frappe.db.set_value("Training Event", self.training_event, "event_status", event_status)
+ if employee:
+ frappe.db.set_value("Training Event Employee", employee, "status", "Feedback Submitted")
+
+ def on_cancel(self):
+ employee = frappe.db.get_value("Training Event Employee", {
+ "parent": self.training_event,
+ "employee": self.employee
+ })
+
+ if employee:
+ frappe.db.set_value("Training Event Employee", employee, "status", "Completed")
diff --git a/erpnext/hr/doctype/training_program/training_program.js b/erpnext/hr/doctype/training_program/training_program.js
index 7d85cab59dc..a4ccf540636 100644
--- a/erpnext/hr/doctype/training_program/training_program.js
+++ b/erpnext/hr/doctype/training_program/training_program.js
@@ -2,4 +2,4 @@
// For license information, please see license.txt
frappe.ui.form.on('Training Program', {
-});
\ No newline at end of file
+});
diff --git a/erpnext/hr/doctype/training_program/training_program_dashboard.py b/erpnext/hr/doctype/training_program/training_program_dashboard.py
index 441a71bba77..0fc18a80298 100644
--- a/erpnext/hr/doctype/training_program/training_program_dashboard.py
+++ b/erpnext/hr/doctype/training_program/training_program_dashboard.py
@@ -10,4 +10,4 @@ def get_data():
'items': ['Training Event']
},
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/training_result/training_result.js b/erpnext/hr/doctype/training_result/training_result.js
index 62ac383ab78..5cdbcad8058 100644
--- a/erpnext/hr/doctype/training_result/training_result.js
+++ b/erpnext/hr/doctype/training_result/training_result.js
@@ -11,7 +11,7 @@ frappe.ui.form.on('Training Result', {
},
training_event: function(frm) {
- if (frm.doc.training_event && !frm.doc.docstatus && !frm.doc.employees) {
+ if (frm.doc.training_event && !frm.doc.docstatus && !frm.doc.employees) {
frappe.call({
method: "erpnext.hr.doctype.training_result.training_result.get_employees",
args: {
diff --git a/erpnext/hr/doctype/training_result_employee/test_training_result.js b/erpnext/hr/doctype/training_result_employee/test_training_result.js
index 2ebf8962ee2..3f397508357 100644
--- a/erpnext/hr/doctype/training_result_employee/test_training_result.js
+++ b/erpnext/hr/doctype/training_result_employee/test_training_result.js
@@ -50,4 +50,3 @@ QUnit.test("Test: Training Result [HR]", function (assert) {
() => done()
]);
});
-
diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
index 674c8e3eb45..9c765d73716 100644
--- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
@@ -10,7 +10,7 @@ from frappe import _
from frappe.utils.csvutils import UnicodeWriter
from frappe.model.document import Document
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
-from erpnext.hr.utils import get_holidays_for_employee
+from erpnext.hr.utils import get_holiday_dates_for_employee
class UploadAttendance(Document):
pass
@@ -94,7 +94,7 @@ def get_holidays_for_employees(employees, from_date, to_date):
holidays = {}
for employee in employees:
holiday_list = get_holiday_list_for_employee(employee)
- holiday = get_holidays_for_employee(employee, getdate(from_date), getdate(to_date))
+ holiday = get_holiday_dates_for_employee(employee, getdate(from_date), getdate(to_date))
if holiday_list not in holidays:
holidays[holiday_list] = holiday
diff --git a/erpnext/hr/doctype/vehicle/vehicle_dashboard.py b/erpnext/hr/doctype/vehicle/vehicle_dashboard.py
index 761c70182b2..628c8972cec 100644
--- a/erpnext/hr/doctype/vehicle/vehicle_dashboard.py
+++ b/erpnext/hr/doctype/vehicle/vehicle_dashboard.py
@@ -17,4 +17,4 @@ def get_data():
'items': ['Delivery Trip']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
index ed52c4e1222..ed02120cca3 100644
--- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
+++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
@@ -115,4 +115,4 @@ def make_vehicle_log(license_plate, employee_id, with_services=False):
vehicle_log.save()
vehicle_log.submit()
- return vehicle_log
\ No newline at end of file
+ return vehicle_log
diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.js b/erpnext/hr/doctype/vehicle_log/vehicle_log.js
index 6f3a0dc40eb..14fe9a02da2 100644
--- a/erpnext/hr/doctype/vehicle_log/vehicle_log.js
+++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.js
@@ -24,4 +24,3 @@ frappe.ui.form.on("Vehicle Log", {
});
}
});
-
diff --git a/erpnext/hr/notification/training_feedback/training_feedback.html b/erpnext/hr/notification/training_feedback/training_feedback.html
index fd8fef9e82c..b49662a6eb9 100644
--- a/erpnext/hr/notification/training_feedback/training_feedback.html
+++ b/erpnext/hr/notification/training_feedback/training_feedback.html
@@ -3,4 +3,4 @@
You attended training {{ frappe.utils.get_link_to_form(
"Training Event", doc.training_event) }}
-
{{ _("Please share your feedback to the training by clicking on 'Training Feedback' and then 'New'") }}
\ No newline at end of file
+
{{ _("Please share your feedback to the training by clicking on 'Training Feedback' and then 'New'") }}
-
\ No newline at end of file
+
diff --git a/erpnext/stock/doctype/item/tests/test_item.js b/erpnext/stock/doctype/item/tests/test_item.js
index 5e3524e5b6d..7f7e72d5c0f 100644
--- a/erpnext/stock/doctype/item/tests/test_item.js
+++ b/erpnext/stock/doctype/item/tests/test_item.js
@@ -118,4 +118,4 @@ QUnit.test("test: item", function (assert) {
),
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/stock/doctype/item_attribute/test_item_attribute.py b/erpnext/stock/doctype/item_attribute/test_item_attribute.py
index 61e53d24a46..07af176a944 100644
--- a/erpnext/stock/doctype/item_attribute/test_item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/test_item_attribute.py
@@ -28,4 +28,3 @@ class TestItemAttribute(unittest.TestCase):
item_attribute.increment = 0.5
item_attribute.save()
-
diff --git a/erpnext/stock/doctype/item_customer_detail/item_customer_detail.py b/erpnext/stock/doctype/item_customer_detail/item_customer_detail.py
index a9183ce5866..3e4e8500467 100644
--- a/erpnext/stock/doctype/item_customer_detail/item_customer_detail.py
+++ b/erpnext/stock/doctype/item_customer_detail/item_customer_detail.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class ItemCustomerDetail(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
index c27d1be7892..939abf8d324 100644
--- a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
+++ b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
@@ -65,4 +65,4 @@ class ItemManufacturer(Document):
@frappe.whitelist()
def get_item_manufacturer_part_no(item_code, manufacturer):
return frappe.db.get_value("Item Manufacturer",
- {'item_code': item_code, 'manufacturer': manufacturer}, 'manufacturer_part_no')
\ No newline at end of file
+ {'item_code': item_code, 'manufacturer': manufacturer}, 'manufacturer_part_no')
diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.py b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.py
index 92aefc8d9e9..785737b267f 100644
--- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.py
+++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class ItemQualityInspectionParameter(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/item_reorder/item_reorder.py b/erpnext/stock/doctype/item_reorder/item_reorder.py
index 0f9c593d36a..5cdaa229565 100644
--- a/erpnext/stock/doctype/item_reorder/item_reorder.py
+++ b/erpnext/stock/doctype/item_reorder/item_reorder.py
@@ -9,4 +9,4 @@ import frappe
from frappe.model.document import Document
class ItemReorder(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/item_supplier/item_supplier.py b/erpnext/stock/doctype/item_supplier/item_supplier.py
index 1a07f03ec58..5dda535f810 100644
--- a/erpnext/stock/doctype/item_supplier/item_supplier.py
+++ b/erpnext/stock/doctype/item_supplier/item_supplier.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class ItemSupplier(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/item_tax/item_tax.py b/erpnext/stock/doctype/item_tax/item_tax.py
index 1fe2f454681..7c9e8115758 100644
--- a/erpnext/stock/doctype/item_tax/item_tax.py
+++ b/erpnext/stock/doctype/item_tax/item_tax.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class ItemTax(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/item_website_specification/item_website_specification.py b/erpnext/stock/doctype/item_website_specification/item_website_specification.py
index 6d0dbad2a5e..e3041cf3eef 100644
--- a/erpnext/stock/doctype/item_website_specification/item_website_specification.py
+++ b/erpnext/stock/doctype/item_website_specification/item_website_specification.py
@@ -9,4 +9,4 @@ import frappe
from frappe.model.document import Document
class ItemWebsiteSpecification(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py
index 0521a7ad1cb..493e8b239a4 100644
--- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py
+++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class LandedCostItem(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py
index f7ccb9b6e29..38f4eafc3aa 100644
--- a/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py
+++ b/erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class LandedCostPurchaseReceipt(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.py b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.py
index e4458207dbb..0dc396aefac 100644
--- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.py
+++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.py
@@ -6,4 +6,4 @@ import frappe
from frappe.model.document import Document
class LandedCostTaxesandCharges(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 6e66f9869c0..087a7883e09 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -434,7 +434,7 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
if (doc.material_request_type == "Customer Provided") {
return{
query: "erpnext.controllers.queries.item_query",
- filters:{
+ filters:{
'customer': me.frm.doc.customer,
'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 4e2d9e61704..7cdf5b7e8f0 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -133,7 +133,8 @@
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "options": "Barcode"
},
{
"allow_bulk_edit": 1,
@@ -314,7 +315,7 @@
"idx": 70,
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 23:52:55.392512",
+ "modified": "2021-08-17 20:16:12.737743",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request",
diff --git a/erpnext/stock/doctype/material_request/material_request_dashboard.py b/erpnext/stock/doctype/material_request/material_request_dashboard.py
index f3e5e5db250..e1e4faf6825 100644
--- a/erpnext/stock/doctype/material_request/material_request_dashboard.py
+++ b/erpnext/stock/doctype/material_request/material_request_dashboard.py
@@ -20,4 +20,4 @@ def get_data():
'items': ['Work Order']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/stock/doctype/material_request/tests/test_material_request.js b/erpnext/stock/doctype/material_request/tests/test_material_request.js
index bf26cd117f8..a2cd03b6495 100644
--- a/erpnext/stock/doctype/material_request/tests/test_material_request.js
+++ b/erpnext/stock/doctype/material_request/tests/test_material_request.js
@@ -37,4 +37,3 @@ QUnit.test("test material request", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/material_request/tests/test_material_request_from_bom.js b/erpnext/stock/doctype/material_request/tests/test_material_request_from_bom.js
index d8b39fe5aaf..6fb55ae02ac 100644
--- a/erpnext/stock/doctype/material_request/tests/test_material_request_from_bom.js
+++ b/erpnext/stock/doctype/material_request/tests/test_material_request_from_bom.js
@@ -25,4 +25,3 @@ QUnit.test("test material request get items from BOM", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/material_request/tests/test_material_request_type_manufacture.js b/erpnext/stock/doctype/material_request/tests/test_material_request_type_manufacture.js
index 91b47bac4d9..137079b9838 100644
--- a/erpnext/stock/doctype/material_request/tests/test_material_request_type_manufacture.js
+++ b/erpnext/stock/doctype/material_request/tests/test_material_request_type_manufacture.js
@@ -27,4 +27,3 @@ QUnit.test("test material request", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_issue.js b/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_issue.js
index 050e0f0d1c9..b03a8543c6f 100644
--- a/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_issue.js
+++ b/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_issue.js
@@ -27,4 +27,3 @@ QUnit.test("test material request for issue", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_transfer.js b/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_transfer.js
index d6f9b661414..7c62c2e63a1 100644
--- a/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_transfer.js
+++ b/erpnext/stock/doctype/material_request/tests/test_material_request_type_material_transfer.js
@@ -27,4 +27,3 @@ QUnit.test("test material request for transfer", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.py b/erpnext/stock/doctype/material_request_item/material_request_item.py
index 16f007f6a20..e0066e65d2c 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.py
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.py
@@ -12,4 +12,4 @@ class MaterialRequestItem(Document):
pass
def on_doctype_update():
- frappe.db.add_index("Material Request Item", ["item_code", "warehouse"])
\ No newline at end of file
+ frappe.db.add_index("Material Request Item", ["item_code", "warehouse"])
diff --git a/erpnext/stock/doctype/packing_slip_item/packing_slip_item.py b/erpnext/stock/doctype/packing_slip_item/packing_slip_item.py
index 694ab384bf6..b0a855961f9 100644
--- a/erpnext/stock/doctype/packing_slip_item/packing_slip_item.py
+++ b/erpnext/stock/doctype/packing_slip_item/packing_slip_item.py
@@ -9,4 +9,4 @@ import frappe
from frappe.model.document import Document
class PackingSlipItem(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index ee218f2f685..730fd7a829c 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -201,4 +201,4 @@ function get_item_details(item_code, uom=null) {
uom
});
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py
index 6e007df5e6b..7c321c450a6 100644
--- a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py
+++ b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Stock Entry', 'Delivery Note']
},
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/stock/doctype/price_list/price_list.css b/erpnext/stock/doctype/price_list/price_list.css
index 61b069442f8..6832954a811 100644
--- a/erpnext/stock/doctype/price_list/price_list.css
+++ b/erpnext/stock/doctype/price_list/price_list.css
@@ -4,4 +4,4 @@
.table-grid thead tr {
height: 50px;
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/price_list/price_list.js b/erpnext/stock/doctype/price_list/price_list.js
index c362b5a765c..9291498e863 100644
--- a/erpnext/stock/doctype/price_list/price_list.js
+++ b/erpnext/stock/doctype/price_list/price_list.js
@@ -11,4 +11,4 @@ frappe.ui.form.on("Price List", {
frappe.set_route("Report", "Item Price");
}, "fa fa-money");
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py
index 33713faf696..002d3d898eb 100644
--- a/erpnext/stock/doctype/price_list/price_list.py
+++ b/erpnext/stock/doctype/price_list/price_list.py
@@ -13,6 +13,9 @@ class PriceList(Document):
if not cint(self.buying) and not cint(self.selling):
throw(_("Price List must be applicable for Buying or Selling"))
+ if not self.is_new():
+ self.check_impact_on_shopping_cart()
+
def on_update(self):
self.set_default_if_missing()
self.update_item_price()
@@ -32,6 +35,17 @@ class PriceList(Document):
buying=%s, selling=%s, modified=NOW() where price_list=%s""",
(self.currency, cint(self.buying), cint(self.selling), self.name))
+ def check_impact_on_shopping_cart(self):
+ "Check if Price List currency change impacts Shopping Cart."
+ from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import validate_cart_settings
+
+ doc_before_save = self.get_doc_before_save()
+ currency_changed = self.currency != doc_before_save.currency
+ affects_cart = self.name == frappe.get_cached_value("Shopping Cart Settings", None, "price_list")
+
+ if currency_changed and affects_cart:
+ validate_cart_settings()
+
def on_trash(self):
self.delete_price_list_details_key()
@@ -62,4 +76,4 @@ def get_price_list_details(price_list):
frappe.cache().hset("price_list_details", price_list, price_list_details)
- return price_list_details or {}
\ No newline at end of file
+ return price_list_details or {}
diff --git a/erpnext/stock/doctype/price_list/test_price_list.py b/erpnext/stock/doctype/price_list/test_price_list.py
index 5979c861294..2c287c9033c 100644
--- a/erpnext/stock/doctype/price_list/test_price_list.py
+++ b/erpnext/stock/doctype/price_list/test_price_list.py
@@ -6,4 +6,4 @@ import frappe
# test_ignore = ["Item"]
-test_records = frappe.get_test_records('Price List')
\ No newline at end of file
+test_records = frappe.get_test_records('Price List')
diff --git a/erpnext/stock/doctype/price_list/test_price_list_uom.js b/erpnext/stock/doctype/price_list/test_price_list_uom.js
index 7fbce7d59d2..3896c0e59ea 100644
--- a/erpnext/stock/doctype/price_list/test_price_list_uom.js
+++ b/erpnext/stock/doctype/price_list/test_price_list_uom.js
@@ -55,4 +55,4 @@ QUnit.test("test price list with uom dependancy", function(assert) {
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 44fb736304f..1a597343c0a 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -1098,7 +1098,8 @@
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "options": "Barcode"
},
{
"fieldname": "billing_address",
@@ -1148,7 +1149,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2021-05-25 00:15:12.239017",
+ "modified": "2021-08-17 20:16:40.849885",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/regional/india.js b/erpnext/stock/doctype/purchase_receipt/regional/india.js
index b4f1201f36c..2d982cc1bb3 100644
--- a/erpnext/stock/doctype/purchase_receipt/regional/india.js
+++ b/erpnext/stock/doctype/purchase_receipt/regional/india.js
@@ -1,3 +1,3 @@
{% include "erpnext/regional/india/taxes.js" %}
-erpnext.setup_auto_gst_taxation('Purchase Receipt');
\ No newline at end of file
+erpnext.setup_auto_gst_taxation('Purchase Receipt');
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
index 0f50bcd6ea8..315e723fabc 100644
--- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
@@ -232,4 +232,4 @@ def get_serial_nos_to_allocate(serial_nos, to_allocate):
allocated_serial_nos = serial_nos[0: cint(to_allocate)]
serial_nos[:] = serial_nos[cint(to_allocate):] # pop out allocated serial nos and modify list
return "\n".join(allocated_serial_nos) if allocated_serial_nos else ""
- else: return ""
\ No newline at end of file
+ else: return ""
diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
index 86f7dc3e084..0590ae1abeb 100644
--- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
@@ -386,4 +386,4 @@ def create_putaway_rule(**args):
if not args.do_not_save:
putaway.save()
- return putaway
\ No newline at end of file
+ return putaway
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js
index f7565fd505c..d08dc3e8b76 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js
@@ -81,4 +81,4 @@ frappe.ui.form.on("Quality Inspection", {
});
}
},
-});
\ No newline at end of file
+});
diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.py b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.py
index 65188a22c6e..b10fa310d66 100644
--- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.py
+++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class QualityInspectionReading(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
index 01d2031b3a4..971b3c29825 100644
--- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
+++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
@@ -16,4 +16,4 @@ def get_template_details(template):
fields=["specification", "value", "acceptance_formula",
"numeric", "formula_based_criteria", "min_value", "max_value"],
filters={'parenttype': 'Quality Inspection Template', 'parent': template},
- order_by="idx")
\ No newline at end of file
+ order_by="idx")
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index b9a58cf43e4..0eccce3a58e 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -193,4 +193,4 @@ class TestSerialNo(unittest.TestCase):
frappe.db.rollback()
def tearDown(self):
- frappe.db.rollback()
\ No newline at end of file
+ frappe.db.rollback()
diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js
index ce2906ecbe9..13a17a25913 100644
--- a/erpnext/stock/doctype/shipment/shipment.js
+++ b/erpnext/stock/doctype/shipment/shipment.js
@@ -150,8 +150,8 @@ frappe.ui.form.on('Shipment', {
frm.set_value('pickup_contact_name', '');
frm.set_value('pickup_contact', '');
}
- frappe.throw(__("Email or Phone/Mobile of the Contact are mandatory to continue.")
- + "" + __("Please set Email/Phone for the contact")
+ frappe.throw(__("Email or Phone/Mobile of the Contact are mandatory to continue.")
+ + "" + __("Please set Email/Phone for the contact")
+ ` ${contact_name}`);
}
let contact_display = r.message.contact_display;
@@ -244,8 +244,8 @@ frappe.ui.form.on('Shipment', {
frm.set_value('pickup_company', '');
frm.set_value('pickup_contact', '');
}
- frappe.throw(__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") + ""
- + __("Please first set Last Name, Email and Phone for the user")
+ frappe.throw(__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") + ""
+ + __("Please first set Last Name, Email and Phone for the user")
+ ` ${frappe.session.user}`);
}
let contact_display = r.full_name;
diff --git a/erpnext/stock/doctype/shipment/shipment_list.js b/erpnext/stock/doctype/shipment/shipment_list.js
index 52b052c81f3..ae6a3c154e8 100644
--- a/erpnext/stock/doctype/shipment/shipment_list.js
+++ b/erpnext/stock/doctype/shipment/shipment_list.js
@@ -5,4 +5,4 @@ frappe.listview_settings['Shipment'] = {
return [__("Booked"), "green"];
}
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py
index 9c3e22f0231..db2f1161743 100644
--- a/erpnext/stock/doctype/shipment/test_shipment.py
+++ b/erpnext/stock/doctype/shipment/test_shipment.py
@@ -24,7 +24,7 @@ def create_test_delivery_note():
customer = get_shipment_customer()
item = get_shipment_item(company.name)
posting_date = date.today() + timedelta(days=1)
-
+
create_material_receipt(item, company.name)
delivery_note = frappe.new_doc("Delivery Note")
delivery_note.company = company.name
@@ -73,7 +73,7 @@ def create_test_shipment(delivery_notes = None):
shipment.pickup_to = '17:00'
shipment.description_of_content = 'unit test entry'
for delivery_note in delivery_notes:
- shipment.append('shipment_delivery_note',
+ shipment.append('shipment_delivery_note',
{
"delivery_note": delivery_note.name
}
@@ -222,7 +222,7 @@ def create_material_receipt(item, company):
)
stock.insert()
stock.submit()
-
+
def create_shipment_item(item_name, company_name):
item = frappe.new_doc("Item")
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 67083930272..efbc12ce841 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -1101,3 +1101,4 @@ function check_should_not_attach_bom_items(bom_no) {
}
$.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm}));
+
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index 523d332b8f4..2f377788961 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -84,8 +84,6 @@
"oldfieldtype": "Section Break"
},
{
- "allow_on_submit": 1,
- "default": "{purpose}",
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
@@ -355,6 +353,7 @@
},
{
"fieldname": "scan_barcode",
+ "options": "Barcode",
"fieldtype": "Data",
"label": "Scan Barcode"
},
@@ -629,7 +628,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-05-26 17:07:58.015737",
+ "modified": "2021-08-20 19:19:31.514846",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 3ff42bf9742..ba7c6d11337 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -58,6 +58,7 @@ class StockEntry(StockController):
self.validate_posting_time()
self.validate_purpose()
+ self.set_title()
self.validate_item()
self.validate_customer_provided_item()
self.validate_qty()
@@ -271,7 +272,7 @@ class StockEntry(StockController):
item_wise_qty = {}
if self.purpose == "Manufacture" and self.work_order:
for d in self.items:
- if d.is_finished_item:
+ if d.is_finished_item or d.is_process_loss:
item_wise_qty.setdefault(d.item_code, []).append(d.qty)
for item_code, qty_list in iteritems(item_wise_qty):
@@ -317,9 +318,6 @@ class StockEntry(StockController):
d.s_warehouse = self.from_warehouse
d.t_warehouse = self.to_warehouse
- if not (d.s_warehouse or d.t_warehouse):
- frappe.throw(_("Atleast one warehouse is mandatory"))
-
if self.purpose in source_mandatory and not d.s_warehouse:
if self.from_warehouse:
d.s_warehouse = self.from_warehouse
@@ -332,9 +330,10 @@ class StockEntry(StockController):
else:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
+
if self.purpose == "Manufacture":
if validate_for_manufacture:
- if d.is_finished_item or d.is_scrap_item:
+ if d.is_finished_item or d.is_scrap_item or d.is_process_loss:
d.s_warehouse = None
if not d.t_warehouse:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
@@ -346,6 +345,9 @@ class StockEntry(StockController):
if cstr(d.s_warehouse) == cstr(d.t_warehouse) and not self.purpose == "Material Transfer for Manufacture":
frappe.throw(_("Source and target warehouse cannot be same for row {0}").format(d.idx))
+ if not (d.s_warehouse or d.t_warehouse):
+ frappe.throw(_("Atleast one warehouse is mandatory"))
+
def validate_work_order(self):
if self.purpose in ("Manufacture", "Material Transfer for Manufacture", "Material Consumption for Manufacture"):
# check if work order is entered
@@ -463,7 +465,7 @@ class StockEntry(StockController):
"""
# Set rate for outgoing items
outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate)
- finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
+ finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item or d.is_process_loss)
# Set basic rate for incoming items
for d in self.get('items'):
@@ -484,6 +486,8 @@ class StockEntry(StockController):
raise_error_if_no_rate=raise_error_if_no_rate)
d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
+ if d.is_process_loss:
+ d.basic_rate = flt(0.)
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
@@ -1041,6 +1045,7 @@ class StockEntry(StockController):
self.set_scrap_items()
self.set_actual_qty()
+ self.update_items_for_process_loss()
self.validate_customer_provided_item()
self.calculate_rate_and_amount()
@@ -1185,7 +1190,7 @@ class StockEntry(StockController):
wo = frappe.get_doc("Work Order", self.work_order)
wo_items = frappe.get_all('Work Order Item',
filters={'parent': self.work_order},
- fields=["item_code", "required_qty", "consumed_qty", "transferred_qty"]
+ fields=["item_code", "source_warehouse", "required_qty", "consumed_qty", "transferred_qty"]
)
work_order_qty = wo.material_transferred_for_manufacturing or wo.qty
@@ -1205,7 +1210,7 @@ class StockEntry(StockController):
if qty > 0:
self.add_to_stock_entry_detail({
item.item_code: {
- "from_warehouse": wo.wip_warehouse,
+ "from_warehouse": wo.wip_warehouse or item.source_warehouse,
"to_warehouse": "",
"qty": qty,
"item_name": item.item_name,
@@ -1398,6 +1403,7 @@ class StockEntry(StockController):
get_default_cost_center(item_dict[d], company = self.company))
se_child.is_finished_item = item_dict[d].get("is_finished_item", 0)
se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0)
+ se_child.is_process_loss = item_dict[d].get("is_process_loss", 0)
for field in ["idx", "po_detail", "original_item",
"expense_account", "description", "item_name", "serial_no", "batch_no"]:
@@ -1576,6 +1582,30 @@ class StockEntry(StockController):
if material_request and material_request not in material_requests:
material_requests.append(material_request)
frappe.db.set_value('Material Request', material_request, 'transfer_status', status)
+
+ def update_items_for_process_loss(self):
+ process_loss_dict = {}
+ for d in self.get("items"):
+ if not d.is_process_loss:
+ continue
+
+ scrap_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_scrap_warehouse")
+ if scrap_warehouse is not None:
+ d.t_warehouse = scrap_warehouse
+ d.is_scrap_item = 0
+
+ if d.item_code not in process_loss_dict:
+ process_loss_dict[d.item_code] = [flt(0), flt(0)]
+ process_loss_dict[d.item_code][0] += flt(d.transfer_qty)
+ process_loss_dict[d.item_code][1] += flt(d.qty)
+
+ for d in self.get("items"):
+ if not d.is_finished_item or d.item_code not in process_loss_dict:
+ continue
+ # Assumption: 1 finished item has 1 row.
+ d.transfer_qty -= process_loss_dict[d.item_code][0]
+ d.qty -= process_loss_dict[d.item_code][1]
+
def set_serial_no_batch_for_finished_good(self):
args = {}
@@ -1607,6 +1637,14 @@ class StockEntry(StockController):
return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos)))
+ def set_title(self):
+ if frappe.flags.in_import and self.title:
+ # Allow updating title during data import/update
+ return
+
+ self.title = self.purpose
+
+
@frappe.whitelist()
def move_sample_to_retention_warehouse(company, items):
if isinstance(items, string_types):
@@ -1857,4 +1895,4 @@ def get_supplied_items(purchase_order):
supplied_item.total_supplied_qty = flt(supplied_item.supplied_qty) - flt(supplied_item.returned_qty)
- return supplied_item_details
\ No newline at end of file
+ return supplied_item_details
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js
new file mode 100644
index 00000000000..285ae4f59e8
--- /dev/null
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js
@@ -0,0 +1,27 @@
+QUnit.module('Stock');
+
+QUnit.test("test manufacture from bom", function(assert) {
+ assert.expect(2);
+ let done = assert.async();
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make("Stock Entry", [
+ { purpose: "Manufacture" },
+ { from_bom: 1 },
+ { bom_no: "BOM-_Test Item - Non Whole UOM-001" },
+ { fg_completed_qty: 2 }
+ ]);
+ },
+ () => cur_frm.save(),
+ () => frappe.click_button("Update Rate and Availability"),
+ () => {
+ assert.ok(cur_frm.doc.items[1] === 0.75, " Finished Item Qty correct");
+ assert.ok(cur_frm.doc.items[2] === 0.25, " Process Loss Item Qty correct");
+ },
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+ () => done()
+ ]);
+});
+
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js
index 3cf4861ccb8..a87a7fb7fd8 100644
--- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js
@@ -28,4 +28,3 @@ QUnit.test("test material request", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue_with_serialize_item.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue_with_serialize_item.js
index aac09c30cd5..cae318d8f2c 100644
--- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue_with_serialize_item.js
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue_with_serialize_item.js
@@ -32,4 +32,3 @@ QUnit.test("test material issue", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js
index 828738eb6ca..ef0286fe1b9 100644
--- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js
@@ -29,4 +29,3 @@ QUnit.test("test material request", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js
index ffd06642bf0..54e1ac81211 100644
--- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js
@@ -32,4 +32,3 @@ QUnit.test("test material receipt", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js
index cdeb4ab04a7..fac0b4b8922 100644
--- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js
@@ -31,4 +31,3 @@ QUnit.test("test material request", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js
index e8b2973c457..9f853072709 100644
--- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js
@@ -31,4 +31,3 @@ QUnit.test("test material Transfer to manufacture", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js
index 699634df6d2..20f119ad617 100644
--- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js
@@ -39,4 +39,3 @@ QUnit.test("test repack", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js
index 770f886d043..8243426032d 100644
--- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js
@@ -31,4 +31,3 @@ QUnit.test("test material Transfer to manufacture", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index 22f412a2989..2282b6aa167 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -18,6 +18,7 @@
"col_break2",
"is_finished_item",
"is_scrap_item",
+ "is_process_loss",
"quality_inspection",
"subcontracted_item",
"section_break_8",
@@ -543,13 +544,19 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "is_process_loss",
+ "fieldtype": "Check",
+ "label": "Is Process Loss"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-06-21 16:03:18.834880",
+ "modified": "2021-06-22 16:47:11.268975",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py
index f9e062f8516..a5623fded23 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class StockEntryDetail(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index b4f458388b3..be1f00e37fa 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -55,8 +55,8 @@ class StockLedgerEntry(Document):
"sum(actual_qty)") or 0
frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty)
- #check for item quantity available in stock
def actual_amt_check(self):
+ """Validate that qty at warehouse for selected batch is >=0"""
if self.batch_no and not self.get("allow_negative_stock"):
batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry`
@@ -107,7 +107,7 @@ class StockLedgerEntry(Document):
self.stock_uom = item_det.stock_uom
def check_stock_frozen_date(self):
- stock_settings = frappe.get_doc('Stock Settings', 'Stock Settings')
+ stock_settings = frappe.get_cached_doc('Stock Settings')
if stock_settings.stock_frozen_upto:
if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto)
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 349e59f31d1..99694690bbc 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -288,3 +288,4 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({
});
cur_frm.cscript = new erpnext.stock.StockReconciliation({frm: cur_frm});
+
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 324bb7a62d9..4531652a13c 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -390,7 +390,7 @@ class StockReconciliation(StockController):
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
sl_entries.reverse()
- allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
+ allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js
index 80001d63fd4..666d2c7144f 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js
@@ -29,4 +29,3 @@ QUnit.test("test Stock Reconciliation", function(assert) {
() => done()
]);
});
-
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index c192582531a..e4381271ed2 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -15,6 +15,7 @@ from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from erpnext.tests.utils import change_settings
class TestStockReconciliation(unittest.TestCase):
@@ -310,6 +311,7 @@ class TestStockReconciliation(unittest.TestCase):
pr2.cancel()
pr1.cancel()
+ @change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_backdated_stock_reco_future_negative_stock(self):
"""
Test if a backdated stock reco causes future negative stock and is blocked.
@@ -327,8 +329,6 @@ class TestStockReconciliation(unittest.TestCase):
warehouse = "_Test Warehouse - _TC"
create_item(item_code)
- negative_stock_setting = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
- frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 0)
pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100,
posting_date=add_days(nowdate(), -2))
@@ -348,11 +348,50 @@ class TestStockReconciliation(unittest.TestCase):
self.assertRaises(NegativeStockError, sr3.submit)
# teardown
- frappe.db.set_value("Stock Settings", None, "allow_negative_stock", negative_stock_setting)
sr3.cancel()
dn2.cancel()
pr1.cancel()
+
+ @change_settings("Stock Settings", {"allow_negative_stock": 0})
+ def test_backdated_stock_reco_cancellation_future_negative_stock(self):
+ """
+ Test if a backdated stock reco cancellation that causes future negative stock is blocked.
+ -------------------------------------------
+ Var | Doc | Qty | Balance
+ -------------------------------------------
+ SR | Reco | 100 | 100 (posting date: today-1) (shouldn't be cancelled after DN)
+ DN | DN | 100 | 0 (posting date: today)
+ """
+ from erpnext.stock.stock_ledger import NegativeStockError
+ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+ frappe.db.commit()
+
+ item_code = "Backdated-Reco-Cancellation-Item"
+ warehouse = "_Test Warehouse - _TC"
+ create_item(item_code)
+
+
+ sr = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=100, rate=100,
+ posting_date=add_days(nowdate(), -1))
+
+ dn = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=100, rate=120,
+ posting_date=nowdate())
+
+ dn_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": dn.name, "is_cancelled": 0},
+ "qty_after_transaction")
+ self.assertEqual(dn_balance, 0)
+
+ # check if cancellation of stock reco is blocked
+ self.assertRaises(NegativeStockError, sr.cancel)
+
+ repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name}))
+ self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation")
+
+ # teardown
+ frappe.db.rollback()
+
+
def test_valid_batch(self):
create_batch_item_with_batch("Testing Batch Item 1", "001")
create_batch_item_with_batch("Testing Batch Item 2", "002")
@@ -458,4 +497,3 @@ def set_valuation_method(item_code, valuation_method):
}, allow_negative_stock=1)
test_dependencies = ["Item", "Warehouse"]
-
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js
index 48624e0f25e..6167becdaac 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.js
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.js
@@ -16,36 +16,3 @@ frappe.ui.form.on('Stock Settings', {
}
});
-frappe.tour['Stock Settings'] = [
- {
- fieldname: "item_naming_by",
- title: __("Item Naming By"),
- description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a ") + "Naming Series" + __(" choose the 'Naming Series' option."),
- },
- {
- fieldname: "default_warehouse",
- title: __("Default Warehouse"),
- description: __("Set a Default Warehouse for Inventory Transactions. This will be fetched into the Default Warehouse in the Item master.")
- },
- {
- fieldname: "allow_negative_stock",
- title: __("Allow Negative Stock"),
- description: __("This will allow stock items to be displayed in negative values. Using this option depends on your use case. With this option unchecked, the system warns before obstructing a transaction that is causing negative stock.")
-
- },
- {
- fieldname: "valuation_method",
- title: __("Valuation Method"),
- description: __("Choose between FIFO and Moving Average Valuation Methods. Click ") + "here" + __(" to know more about them.")
- },
- {
- fieldname: "show_barcode_field",
- title: __("Show Barcode Field"),
- description: __("Show 'Scan Barcode' field above every child table to insert Items with ease.")
- },
- {
- fieldname: "automatically_set_serial_nos_based_on_fifo",
- title: __("Automatically Set Serial Nos based on FIFO"),
- description: __("Serial numbers for stock will be set automatically based on the Items entered based on first in first out in transactions like Purchase/Sales Invoices, Delivery Notes, etc.")
- }
-];
diff --git a/erpnext/stock/doctype/uom_conversion_detail/uom_conversion_detail.py b/erpnext/stock/doctype/uom_conversion_detail/uom_conversion_detail.py
index 67fe20bd379..fdead205670 100644
--- a/erpnext/stock/doctype/uom_conversion_detail/uom_conversion_detail.py
+++ b/erpnext/stock/doctype/uom_conversion_detail/uom_conversion_detail.py
@@ -7,4 +7,4 @@ import frappe
from frappe.model.document import Document
class UOMConversionDetail(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.js b/erpnext/stock/doctype/warehouse/test_warehouse.js
index 8ea280cc59e..850da1ee45f 100644
--- a/erpnext/stock/doctype/warehouse/test_warehouse.js
+++ b/erpnext/stock/doctype/warehouse/test_warehouse.js
@@ -16,4 +16,4 @@ QUnit.test("test: warehouse", function (assert) {
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py
index e3981c913e1..6e429a22552 100644
--- a/erpnext/stock/doctype/warehouse/test_warehouse.py
+++ b/erpnext/stock/doctype/warehouse/test_warehouse.py
@@ -180,4 +180,4 @@ def get_group_stock_account(company, company_abbr=None):
if not company_abbr:
company_abbr = frappe.get_cached_value("Company", company, 'abbr')
group_stock_account = "Current Assets - " + company_abbr
- return group_stock_account
\ No newline at end of file
+ return group_stock_account
diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js
index 1f172504a7f..4e1679c4116 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.js
+++ b/erpnext/stock/doctype/warehouse/warehouse.js
@@ -48,11 +48,11 @@ frappe.ui.form.on("Warehouse", {
frm.add_custom_button(__('Non-Group to Group'),
function() { convert_to_group_or_ledger(frm); }, 'fa fa-retweet', 'btn-default')
}
-
+
frm.toggle_enable(['is_group', 'company'], false);
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Warehouse'};
-
+
frm.fields_dict['parent_warehouse'].get_query = function(doc) {
return {
filters: {
@@ -83,6 +83,7 @@ function convert_to_group_or_ledger(frm){
callback: function(){
frm.refresh();
}
-
+
})
-}
\ No newline at end of file
+}
+
diff --git a/erpnext/stock/doctype/warehouse/warehouse_tree.js b/erpnext/stock/doctype/warehouse/warehouse_tree.js
index 407d7d1ccd5..e9e14c72466 100644
--- a/erpnext/stock/doctype/warehouse/warehouse_tree.js
+++ b/erpnext/stock/doctype/warehouse/warehouse_tree.js
@@ -24,4 +24,4 @@ frappe.treeview_settings['Warehouse'] = {
+ '').insertBefore(node.$ul);
}
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/form_tour/stock_entry/stock_entry.json b/erpnext/stock/form_tour/stock_entry/stock_entry.json
new file mode 100644
index 00000000000..6363c6ad4dd
--- /dev/null
+++ b/erpnext/stock/form_tour/stock_entry/stock_entry.json
@@ -0,0 +1,56 @@
+{
+ "creation": "2021-08-24 14:44:22.292652",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-08-25 16:31:31.441194",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock Entry",
+ "owner": "Administrator",
+ "reference_doctype": "Stock Entry",
+ "save_on_complete": 1,
+ "steps": [
+ {
+ "description": "Select the type of Stock Entry to be made. For now, to receive stock into a warehouses select Material Receipt.",
+ "field": "",
+ "fieldname": "stock_entry_type",
+ "fieldtype": "Link",
+ "has_next_condition": 1,
+ "is_table_field": 0,
+ "label": "Stock Entry Type",
+ "next_step_condition": "eval: doc.stock_entry_type === \"Material Receipt\"",
+ "parent_field": "",
+ "position": "Top",
+ "title": "Stock Entry Type"
+ },
+ {
+ "description": "Select a target warehouse where the stock will be received.",
+ "field": "",
+ "fieldname": "to_warehouse",
+ "fieldtype": "Link",
+ "has_next_condition": 1,
+ "is_table_field": 0,
+ "label": "Default Target Warehouse",
+ "next_step_condition": "eval: doc.to_warehouse",
+ "parent_field": "",
+ "position": "Top",
+ "title": "Default Target Warehouse"
+ },
+ {
+ "description": "Select an item and entry quantity to be delivered.",
+ "field": "",
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "has_next_condition": 1,
+ "is_table_field": 0,
+ "label": "Items",
+ "next_step_condition": "eval: doc.items[0]?.item_code",
+ "parent_field": "",
+ "position": "Top",
+ "title": "Items"
+ }
+ ],
+ "title": "Stock Entry"
+}
\ No newline at end of file
diff --git a/erpnext/stock/form_tour/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/form_tour/stock_reconciliation/stock_reconciliation.json
new file mode 100644
index 00000000000..5b7fd72c082
--- /dev/null
+++ b/erpnext/stock/form_tour/stock_reconciliation/stock_reconciliation.json
@@ -0,0 +1,55 @@
+{
+ "creation": "2021-08-24 14:44:46.770952",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-08-25 16:26:11.718664",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock Reconciliation",
+ "owner": "Administrator",
+ "reference_doctype": "Stock Reconciliation",
+ "save_on_complete": 1,
+ "steps": [
+ {
+ "description": "Set Purpose to Opening Stock to set the stock opening balance.",
+ "field": "",
+ "fieldname": "purpose",
+ "fieldtype": "Select",
+ "has_next_condition": 1,
+ "is_table_field": 0,
+ "label": "Purpose",
+ "next_step_condition": "eval: doc.purpose === \"Opening Stock\"",
+ "parent_field": "",
+ "position": "Top",
+ "title": "Purpose"
+ },
+ {
+ "description": "Select the items for which the opening stock has to be set.",
+ "field": "",
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "has_next_condition": 1,
+ "is_table_field": 0,
+ "label": "Items",
+ "next_step_condition": "eval: doc.items[0]?.item_code",
+ "parent_field": "",
+ "position": "Top",
+ "title": "Items"
+ },
+ {
+ "description": "Edit the Posting Date by clicking on the Edit Posting Date and Time checkbox below.",
+ "field": "",
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Posting Date",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Posting Date"
+ }
+ ],
+ "title": "Stock Reconciliation"
+}
\ No newline at end of file
diff --git a/erpnext/stock/form_tour/stock_settings/stock_settings.json b/erpnext/stock/form_tour/stock_settings/stock_settings.json
new file mode 100644
index 00000000000..3d164e33b3b
--- /dev/null
+++ b/erpnext/stock/form_tour/stock_settings/stock_settings.json
@@ -0,0 +1,89 @@
+{
+ "creation": "2021-08-20 15:20:59.336585",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-08-25 16:19:37.699528",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock Settings",
+ "owner": "Administrator",
+ "reference_doctype": "Stock Settings",
+ "save_on_complete": 1,
+ "steps": [
+ {
+ "description": "By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a Naming Series choose the 'Naming Series' option.",
+ "field": "",
+ "fieldname": "item_naming_by",
+ "fieldtype": "Select",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Item Naming By",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Item Naming By"
+ },
+ {
+ "description": "Set a Default Warehouse for Inventory Transactions. This will be fetched into the Default Warehouse in the Item master.",
+ "field": "",
+ "fieldname": "default_warehouse",
+ "fieldtype": "Link",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Default Warehouse",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Default Warehouse"
+ },
+ {
+ "description": "Quality inspection is performed on the inward and outward movement of goods. Receipt and delivery transactions will be stopped or the user will be warned if the quality inspection is not performed.",
+ "field": "",
+ "fieldname": "action_if_quality_inspection_is_not_submitted",
+ "fieldtype": "Select",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Action If Quality Inspection Is Not Submitted",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Action if Quality Inspection Is Not Submitted"
+ },
+ {
+ "description": "Serial numbers for stock will be set automatically based on the Items entered based on first in first out in transactions like Purchase/Sales Invoices, Delivery Notes, etc.",
+ "field": "",
+ "fieldname": "automatically_set_serial_nos_based_on_fifo",
+ "fieldtype": "Check",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Automatically Set Serial Nos Based on FIFO",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Automatically Set Serial Nos based on FIFO"
+ },
+ {
+ "description": "Show 'Scan Barcode' field above every child table to insert Items with ease.",
+ "field": "",
+ "fieldname": "show_barcode_field",
+ "fieldtype": "Check",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Show Barcode Field in Stock Transactions",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Show Barcode Field"
+ },
+ {
+ "description": "Choose between FIFO and Moving Average Valuation Methods. Click here to know more about them.",
+ "field": "",
+ "fieldname": "valuation_method",
+ "fieldtype": "Select",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Default Valuation Method",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Default Valuation Method"
+ }
+ ],
+ "title": "Stock Settings"
+}
\ No newline at end of file
diff --git a/erpnext/stock/form_tour/warehouse/warehouse.json b/erpnext/stock/form_tour/warehouse/warehouse.json
new file mode 100644
index 00000000000..23ff2aebbaa
--- /dev/null
+++ b/erpnext/stock/form_tour/warehouse/warehouse.json
@@ -0,0 +1,54 @@
+{
+ "creation": "2021-08-24 14:43:44.465237",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-08-24 14:50:31.988256",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Warehouse",
+ "owner": "Administrator",
+ "reference_doctype": "Warehouse",
+ "save_on_complete": 1,
+ "steps": [
+ {
+ "description": "Select a name for the warehouse. This should reflect its location or purpose.",
+ "field": "",
+ "fieldname": "warehouse_name",
+ "fieldtype": "Data",
+ "has_next_condition": 1,
+ "is_table_field": 0,
+ "label": "Warehouse Name",
+ "next_step_condition": "eval: doc.warehouse_name",
+ "parent_field": "",
+ "position": "Bottom",
+ "title": "Warehouse Name"
+ },
+ {
+ "description": "Select a warehouse type to categorize the warehouse into a sub-group.",
+ "field": "",
+ "fieldname": "warehouse_type",
+ "fieldtype": "Link",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Warehouse Type",
+ "parent_field": "",
+ "position": "Top",
+ "title": "Warehouse Type"
+ },
+ {
+ "description": "Select an account to set a default account for all transactions with this warehouse.",
+ "field": "",
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "has_next_condition": 0,
+ "is_table_field": 0,
+ "label": "Account",
+ "parent_field": "",
+ "position": "Top",
+ "title": "Account"
+ }
+ ],
+ "title": "Warehouse"
+}
\ No newline at end of file
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index a0fbcecc5de..c72073c6143 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -278,6 +278,10 @@ def get_basic_details(args, item, overwrite_warehouse=True):
else:
args.uom = item.stock_uom
+ if (args.get("batch_no") and
+ item.name != frappe.get_cached_value('Batch', args.get("batch_no"), 'item')):
+ args['batch_no'] = ''
+
out = frappe._dict({
"item_code": item.name,
"item_name": item.item_name,
diff --git a/erpnext/stock/landed_taxes_and_charges_common.js b/erpnext/stock/landed_taxes_and_charges_common.js
index f3f61963a88..ff8a69fb033 100644
--- a/erpnext/stock/landed_taxes_and_charges_common.js
+++ b/erpnext/stock/landed_taxes_and_charges_common.js
@@ -59,4 +59,3 @@ document_list.forEach((doctype) => {
}
});
});
-
diff --git a/erpnext/stock/module_onboarding/stock/stock.json b/erpnext/stock/module_onboarding/stock/stock.json
index 847464822b4..c246747a5b3 100644
--- a/erpnext/stock/module_onboarding/stock/stock.json
+++ b/erpnext/stock/module_onboarding/stock/stock.json
@@ -19,32 +19,26 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/stock",
"idx": 0,
"is_complete": 0,
- "modified": "2020-10-14 14:54:42.741971",
+ "modified": "2021-08-20 14:38:55.570067",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",
"owner": "Administrator",
"steps": [
{
- "step": "Setup your Warehouse"
+ "step": "Stock Settings"
},
{
- "step": "Create a Product"
- },
- {
- "step": "Create a Supplier"
- },
- {
- "step": "Introduction to Stock Entry"
+ "step": "Create a Warehouse"
},
{
"step": "Create a Stock Entry"
},
{
- "step": "Create a Purchase Receipt"
+ "step": "Stock Opening Balance"
},
{
- "step": "Stock Settings"
+ "step": "View Stock Projected Qty"
}
],
"subtitle": "Inventory, Warehouses, Analysis, and more.",
diff --git a/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json b/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json
deleted file mode 100644
index 9012493f57e..00000000000
--- a/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "action": "Create Entry",
- "creation": "2020-05-19 18:59:13.266713",
- "docstatus": 0,
- "doctype": "Onboarding Step",
- "idx": 0,
- "is_complete": 0,
- "is_mandatory": 0,
- "is_single": 0,
- "is_skipped": 0,
- "modified": "2020-10-14 14:53:25.618434",
- "modified_by": "Administrator",
- "name": "Create a Purchase Receipt",
- "owner": "Administrator",
- "reference_document": "Purchase Receipt",
- "show_full_form": 1,
- "title": "Create a Purchase Receipt",
- "validate_action": 1
-}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json b/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json
index 09902b8844e..3cb522c893d 100644
--- a/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json
+++ b/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json
@@ -1,19 +1,21 @@
{
"action": "Create Entry",
+ "action_label": "Create a Material Transfer Entry",
"creation": "2020-05-15 03:20:16.277043",
+ "description": "# Manage Stock Movements\nStock entry allows you to register the movement of stock for various purposes like transfer, received, issues, repacked, etc. To address issues related to theft and pilferages, you can always ensure that the movement of goods happens against a document reference Stock Entry in ERPNext.\n\nLet\u2019s get a quick walk-through on the various scenarios covered in Stock Entry by watching [*this video*](https://www.youtube.com/watch?v=Njt107hlY3I).",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-10-14 14:53:00.105905",
+ "modified": "2021-06-18 13:57:11.434063",
"modified_by": "Administrator",
"name": "Create a Stock Entry",
"owner": "Administrator",
"reference_document": "Stock Entry",
+ "show_form_tour": 1,
"show_full_form": 1,
- "title": "Create a Stock Entry",
+ "title": "Manage Stock Movements",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json
index ef61fa3b2e2..49efe578a29 100644
--- a/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json
+++ b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json
@@ -1,18 +1,19 @@
{
- "action": "Create Entry",
+ "action": "Show Form Tour",
"creation": "2020-05-14 22:09:10.043554",
+ "description": "# Create a Supplier\nIn this step we will create a **Supplier**. If you have already created a **Supplier** you can skip this step.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-10-14 14:53:00.120455",
+ "modified": "2021-05-17 16:37:37.697077",
"modified_by": "Administrator",
"name": "Create a Supplier",
"owner": "Administrator",
"reference_document": "Supplier",
+ "show_form_tour": 0,
"show_full_form": 0,
"title": "Create a Supplier",
"validate_action": 1
diff --git a/erpnext/stock/onboarding_step/create_a_warehouse/create_a_warehouse.json b/erpnext/stock/onboarding_step/create_a_warehouse/create_a_warehouse.json
new file mode 100644
index 00000000000..22c88bf10ea
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_warehouse/create_a_warehouse.json
@@ -0,0 +1,21 @@
+{
+ "action": "Create Entry",
+ "action_label": "Let\u2019s create your first warehouse ",
+ "creation": "2021-05-17 16:13:19.297789",
+ "description": "# Setup a Warehouse\nThe warehouse can be your location/godown/store where you maintain the item's inventory, and receive/deliver them to various parties.\n\nIn ERPNext, you can maintain a Warehouse in the tree structure, so that location and sub-location of an item can be tracked. Also, you can link a Warehouse to a specific Accounting ledger, where the real-time stock value of that warehouse\u2019s item will be reflected.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-18 12:23:36.675572",
+ "modified_by": "Administrator",
+ "name": "Create a Warehouse",
+ "owner": "Administrator",
+ "reference_document": "Warehouse",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Setup a Warehouse",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_an_item/create_an_item.json b/erpnext/stock/onboarding_step/create_an_item/create_an_item.json
new file mode 100644
index 00000000000..016cbd566d5
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_an_item/create_an_item.json
@@ -0,0 +1,22 @@
+{
+ "action": "Create Entry",
+ "action_label": "",
+ "creation": "2021-05-17 13:47:18.515052",
+ "description": "# Create an Item\nThe Stock module deals with the movement of items.\n\nIn this step we will create an [**Item**](https://docs.erpnext.com/docs/user/manual/en/stock/item).",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "intro_video_url": "",
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-05-18 16:15:20.695028",
+ "modified_by": "Administrator",
+ "name": "Create an Item",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Create an Item",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json
index 212e5055eda..384950e8b99 100644
--- a/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json
+++ b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json
@@ -1,17 +1,18 @@
{
"action": "Watch Video",
"creation": "2020-05-15 02:47:17.958806",
+ "description": "# Introduction to Stock Entry\nThis video will give a quick introduction to [**Stock Entry**](https://docs.erpnext.com/docs/user/manual/en/stock/stock-entry).",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-10-14 14:53:00.075177",
+ "modified": "2021-05-18 15:13:43.306064",
"modified_by": "Administrator",
"name": "Introduction to Stock Entry",
"owner": "Administrator",
+ "show_form_tour": 0,
"show_full_form": 0,
"title": "Introduction to Stock Entry",
"validate_action": 1,
diff --git a/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
index 75940ed2a6c..5d33a649100 100644
--- a/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
+++ b/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
@@ -5,15 +5,15 @@
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-10-14 14:53:25.538900",
+ "modified": "2021-05-17 13:53:06.936579",
"modified_by": "Administrator",
"name": "Setup your Warehouse",
"owner": "Administrator",
"path": "Tree/Warehouse",
"reference_document": "Warehouse",
+ "show_form_tour": 0,
"show_full_form": 0,
"title": "Set up your Warehouse",
"validate_action": 1
diff --git a/erpnext/stock/onboarding_step/stock_opening_balance/stock_opening_balance.json b/erpnext/stock/onboarding_step/stock_opening_balance/stock_opening_balance.json
new file mode 100644
index 00000000000..48fd1fddee0
--- /dev/null
+++ b/erpnext/stock/onboarding_step/stock_opening_balance/stock_opening_balance.json
@@ -0,0 +1,22 @@
+{
+ "action": "Create Entry",
+ "action_label": "Let\u2019s create a stock opening entry",
+ "creation": "2021-05-17 16:13:47.511883",
+ "description": "# Update Stock Opening Balance\nIt\u2019s an entry to update the stock balance of an item, in a warehouse, on a date and time you are going live on ERPNext.\n\nOnce opening stocks are updated, you can create transactions like manufacturing and stock deliveries, where this opening stock will be consumed.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-06-18 13:59:36.021097",
+ "modified_by": "Administrator",
+ "name": "Stock Opening Balance",
+ "owner": "Administrator",
+ "reference_document": "Stock Reconciliation",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Update Stock Opening Balance",
+ "validate_action": 1,
+ "video_url": "https://www.youtube.com/watch?v=nlHX0ZZ84Lw"
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/stock_settings/stock_settings.json b/erpnext/stock/onboarding_step/stock_settings/stock_settings.json
index ae34afa695f..2cf90e806cd 100644
--- a/erpnext/stock/onboarding_step/stock_settings/stock_settings.json
+++ b/erpnext/stock/onboarding_step/stock_settings/stock_settings.json
@@ -1,19 +1,21 @@
{
"action": "Show Form Tour",
+ "action_label": "Take a walk through Stock Settings",
"creation": "2020-05-15 02:53:57.209967",
+ "description": "# Review Stock Settings\n\nIn ERPNext, the Stock module\u2019s features are configurable as per your business needs. Stock Settings is the place where you can set your preferences for:\n- Default values for Item and Pricing\n- Default valuation method for inventory valuation\n- Set preference for serialization and batching of item\n- Set tolerance for over-receipt and delivery of items",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
- "is_mandatory": 0,
"is_single": 1,
"is_skipped": 0,
- "modified": "2020-10-14 14:53:00.092504",
+ "modified": "2021-08-18 12:06:51.139387",
"modified_by": "Administrator",
"name": "Stock Settings",
"owner": "Administrator",
"reference_document": "Stock Settings",
+ "show_form_tour": 0,
"show_full_form": 0,
- "title": "Explore Stock Settings",
+ "title": "Review Stock Settings",
"validate_action": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/view_stock_projected_qty/view_stock_projected_qty.json b/erpnext/stock/onboarding_step/view_stock_projected_qty/view_stock_projected_qty.json
new file mode 100644
index 00000000000..e684780751f
--- /dev/null
+++ b/erpnext/stock/onboarding_step/view_stock_projected_qty/view_stock_projected_qty.json
@@ -0,0 +1,24 @@
+{
+ "action": "View Report",
+ "action_label": "Check Stock Projected Qty",
+ "creation": "2021-08-20 14:38:41.649103",
+ "description": "# Check Stock Reports\nBased on the various stock transactions, you can get a host of one-click Stock Reports in ERPNext like Stock Ledger, Stock Balance, Projected Quantity, and Ageing analysis.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-20 14:38:41.649103",
+ "modified_by": "Administrator",
+ "name": "View Stock Projected Qty",
+ "owner": "Administrator",
+ "reference_report": "Stock Projected Qty",
+ "report_description": "You can set the filters to narrow the results, then click on Generate New Report to see the updated report.",
+ "report_reference_doctype": "Item",
+ "report_type": "Script Report",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Check Stock Projected Qty",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/view_warehouses/view_warehouses.json b/erpnext/stock/onboarding_step/view_warehouses/view_warehouses.json
new file mode 100644
index 00000000000..c46c4bdab86
--- /dev/null
+++ b/erpnext/stock/onboarding_step/view_warehouses/view_warehouses.json
@@ -0,0 +1,20 @@
+{
+ "action": "Go to Page",
+ "creation": "2021-05-17 16:12:43.427579",
+ "description": "# View Warehouse\nIn ERPNext the term 'warehouse' can be thought of as a storage location.\n\nWarehouses are arranged in ERPNext in a tree like structure, where multiple sub-warehouses can be grouped under a single warehouse.\n\nIn this step we will view the [**Warehouse Tree**](https://docs.erpnext.com/docs/user/manual/en/stock/warehouse#21-tree-view) to view the [**Warehouses**](https://docs.erpnext.com/docs/user/manual/en/stock/warehouse) that are set by default.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-05-18 15:04:41.198413",
+ "modified_by": "Administrator",
+ "name": "View Warehouses",
+ "owner": "Administrator",
+ "path": "Tree/Warehouse",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "View Warehouses",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html
index 90112c78a83..de7e38e7d3e 100644
--- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html
+++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html
@@ -37,4 +37,4 @@
{% endif %}
-{% endfor %}
\ No newline at end of file
+{% endfor %}
diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
index b610e7dd587..47ae86b9e21 100644
--- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
+++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
@@ -117,4 +117,4 @@ frappe.pages['warehouse-capacity-summary'].on_page_load = function(wrapper) {
setup_click('Item');
setup_click('Warehouse');
});
-};
\ No newline at end of file
+};
diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html
index acaf180a903..7ac5e640302 100644
--- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html
+++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html
@@ -16,4 +16,4 @@
% Occupied
-
\ No newline at end of file
+
diff --git a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
index 7354eee4130..29689b1a912 100644
--- a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
+++ b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
@@ -24,7 +24,7 @@ def execute(filters=None):
data.append([item, item_map[item]["item_name"], item_map[item]["description"], wh, batch,
frappe.db.get_value('Batch', batch, 'expiry_date'), qty_dict.expiry_status
])
-
+
return columns, data
@@ -70,7 +70,7 @@ def get_item_warehouse_batch_map(filters, float_precision):
"expires_on": None, "expiry_status": None}))
qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no]
-
+
expiry_date_unicode = frappe.db.get_value('Batch', d.batch_no, 'expiry_date')
qty_dict.expires_on = expiry_date_unicode
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
index 9e5e63e37e2..da593a40d68 100644
--- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
@@ -64,7 +64,7 @@ def get_data(filters: Filters) -> Data:
assign_self_values(leveled_dict, svd_list)
assign_agg_values(leveled_dict)
-
+
data = []
for item in leveled_dict.items():
i = item[1]
@@ -160,7 +160,7 @@ def get_row(name:str, value:float, is_bold:int, indent:int) -> Row:
if is_bold:
item_group = frappe.bold(item_group)
return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent)
-
+
def assign_item_groups_to_svd_list(svd_list: SVDList) -> None:
ig_map = get_item_groups_map(svd_list)
diff --git a/erpnext/stock/report/delayed_item_report/delayed_item_report.py b/erpnext/stock/report/delayed_item_report/delayed_item_report.py
index 4fc4027200d..61306662c0d 100644
--- a/erpnext/stock/report/delayed_item_report/delayed_item_report.py
+++ b/erpnext/stock/report/delayed_item_report/delayed_item_report.py
@@ -174,4 +174,4 @@ class DelayedItemReport(object):
"fieldname": "po_no",
"fieldtype": "Data",
"width": 100
- }]
\ No newline at end of file
+ }]
diff --git a/erpnext/stock/report/delayed_order_report/delayed_order_report.py b/erpnext/stock/report/delayed_order_report/delayed_order_report.py
index 79dc5d88219..d9151606884 100644
--- a/erpnext/stock/report/delayed_order_report/delayed_order_report.py
+++ b/erpnext/stock/report/delayed_order_report/delayed_order_report.py
@@ -87,4 +87,4 @@ class DelayedOrderReport(DelayedItemReport):
"fieldname": "po_no",
"fieldtype": "Data",
"width": 110
- }]
\ No newline at end of file
+ }]
diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js
index ade004cde42..8a04565c197 100644
--- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js
+++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.js
@@ -6,4 +6,3 @@ frappe.require("assets/erpnext/js/sales_trends_filters.js", function() {
filters: erpnext.get_sales_trends_filters()
}
});
-
diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
index 446d3049b71..77fd2ff2447 100644
--- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
+++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
@@ -47,4 +47,4 @@ def get_chart_data(data, filters):
]
},
"type" : "bar"
- }
\ No newline at end of file
+ }
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
index cf174c93682..00125e71a9a 100644
--- a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
@@ -108,4 +108,4 @@ def get_columns():
'fieldtype': 'Float',
'fieldname': 'differnce',
'width': 110
- }]
\ No newline at end of file
+ }]
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
index e54cf4c66c7..b3b7594ffd7 100644
--- a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
@@ -145,4 +145,4 @@ def get_columns():
'fieldtype': 'Currency',
'fieldname': 'valuation_rate',
'width': 110
- }]
\ No newline at end of file
+ }]
diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py
index a7243878eb8..c8f60a15d64 100644
--- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py
+++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py
@@ -138,4 +138,4 @@ def get_columns(filters):
"fieldtype": "Currency",
"width": "150"
}
- ]
\ No newline at end of file
+ ]
diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.js b/erpnext/stock/report/item_price_stock/item_price_stock.js
index 0bbc61b9dbf..7af1dab6a0b 100644
--- a/erpnext/stock/report/item_price_stock/item_price_stock.js
+++ b/erpnext/stock/report/item_price_stock/item_price_stock.js
@@ -11,4 +11,4 @@ frappe.query_reports["Item Price Stock"] = {
"options": "Item"
}
]
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.py b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
index 086d833bbc4..c67eed7e926 100644
--- a/erpnext/stock/report/item_shortage_report/item_shortage_report.py
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
@@ -158,5 +158,3 @@ def get_columns():
]
return columns
-
-
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.js b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.js
index c0535bf0efa..173aad6d5a9 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.js
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.js
@@ -29,4 +29,4 @@ frappe.query_reports["Itemwise Recommended Reorder Level"] = {
"options": "Brand"
}
]
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/report/process_loss_report/__init__.py b/erpnext/stock/report/process_loss_report/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.js b/erpnext/stock/report/process_loss_report/process_loss_report.js
new file mode 100644
index 00000000000..b0c2b94a254
--- /dev/null
+++ b/erpnext/stock/report/process_loss_report/process_loss_report.js
@@ -0,0 +1,44 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Process Loss Report"] = {
+ filters: [
+ {
+ label: __("Company"),
+ fieldname: "company",
+ fieldtype: "Link",
+ options: "Company",
+ mandatory: true,
+ default: frappe.defaults.get_user_default("Company"),
+ },
+ {
+ label: __("Item"),
+ fieldname: "item",
+ fieldtype: "Link",
+ options: "Item",
+ mandatory: false,
+ },
+ {
+ label: __("Work Order"),
+ fieldname: "work_order",
+ fieldtype: "Link",
+ options: "Work Order",
+ mandatory: false,
+ },
+ {
+ label: __("From Date"),
+ fieldname: "from_date",
+ fieldtype: "Date",
+ mandatory: true,
+ default: frappe.datetime.year_start(),
+ },
+ {
+ label: __("To Date"),
+ fieldname: "to_date",
+ fieldtype: "Date",
+ mandatory: true,
+ default: frappe.datetime.get_today(),
+ },
+ ]
+};
diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.json b/erpnext/stock/report/process_loss_report/process_loss_report.json
new file mode 100644
index 00000000000..afe4aff7f1c
--- /dev/null
+++ b/erpnext/stock/report/process_loss_report/process_loss_report.json
@@ -0,0 +1,29 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-08-24 16:38:15.233395",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-08-24 16:38:15.233395",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Process Loss Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Order",
+ "report_name": "Process Loss Report",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.py b/erpnext/stock/report/process_loss_report/process_loss_report.py
new file mode 100644
index 00000000000..7494328ab43
--- /dev/null
+++ b/erpnext/stock/report/process_loss_report/process_loss_report.py
@@ -0,0 +1,132 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from typing import Dict, List, Tuple
+
+Filters = frappe._dict
+Row = frappe._dict
+Data = List[Row]
+Columns = List[Dict[str, str]]
+QueryArgs = Dict[str, str]
+
+def execute(filters: Filters) -> Tuple[Columns, Data]:
+ columns = get_columns()
+ data = get_data(filters)
+ return columns, data
+
+def get_data(filters: Filters) -> Data:
+ query_args = get_query_args(filters)
+ data = run_query(query_args)
+ update_data_with_total_pl_value(data)
+ return data
+
+def get_columns() -> Columns:
+ return [
+ {
+ 'label': 'Work Order',
+ 'fieldname': 'name',
+ 'fieldtype': 'Link',
+ 'options': 'Work Order',
+ 'width': '200'
+ },
+ {
+ 'label': 'Item',
+ 'fieldname': 'production_item',
+ 'fieldtype': 'Link',
+ 'options': 'Item',
+ 'width': '100'
+ },
+ {
+ 'label': 'Status',
+ 'fieldname': 'status',
+ 'fieldtype': 'Data',
+ 'width': '100'
+ },
+ {
+ 'label': 'Manufactured Qty',
+ 'fieldname': 'produced_qty',
+ 'fieldtype': 'Float',
+ 'width': '150'
+ },
+ {
+ 'label': 'Loss Qty',
+ 'fieldname': 'process_loss_qty',
+ 'fieldtype': 'Float',
+ 'width': '150'
+ },
+ {
+ 'label': 'Actual Manufactured Qty',
+ 'fieldname': 'actual_produced_qty',
+ 'fieldtype': 'Float',
+ 'width': '150'
+ },
+ {
+ 'label': 'Loss Value',
+ 'fieldname': 'total_pl_value',
+ 'fieldtype': 'Float',
+ 'width': '150'
+ },
+ {
+ 'label': 'FG Value',
+ 'fieldname': 'total_fg_value',
+ 'fieldtype': 'Float',
+ 'width': '150'
+ },
+ {
+ 'label': 'Raw Material Value',
+ 'fieldname': 'total_rm_value',
+ 'fieldtype': 'Float',
+ 'width': '150'
+ }
+ ]
+
+def get_query_args(filters: Filters) -> QueryArgs:
+ query_args = {}
+ query_args.update(filters)
+ query_args.update(
+ get_filter_conditions(filters)
+ )
+ return query_args
+
+def run_query(query_args: QueryArgs) -> Data:
+ return frappe.db.sql("""
+ SELECT
+ wo.name, wo.status, wo.production_item, wo.qty,
+ wo.produced_qty, wo.process_loss_qty,
+ (wo.produced_qty - wo.process_loss_qty) as actual_produced_qty,
+ sum(se.total_incoming_value) as total_fg_value,
+ sum(se.total_outgoing_value) as total_rm_value
+ FROM
+ `tabWork Order` wo INNER JOIN `tabStock Entry` se
+ ON wo.name=se.work_order
+ WHERE
+ process_loss_qty > 0
+ AND wo.company = %(company)s
+ AND se.docstatus = 1
+ AND se.posting_date BETWEEN %(from_date)s AND %(to_date)s
+ {item_filter}
+ {work_order_filter}
+ GROUP BY
+ se.work_order
+ """.format(**query_args), query_args, as_dict=1, debug=1)
+
+def update_data_with_total_pl_value(data: Data) -> None:
+ for row in data:
+ value_per_unit_fg = row['total_fg_value'] / row['actual_produced_qty']
+ row['total_pl_value'] = row['process_loss_qty'] * value_per_unit_fg
+
+def get_filter_conditions(filters: Filters) -> QueryArgs:
+ filter_conditions = dict(item_filter="", work_order_filter="")
+ if "item" in filters:
+ production_item = filters.get("item")
+ filter_conditions.update(
+ {"item_filter": f"AND wo.production_item='{production_item}'"}
+ )
+ if "work_order" in filters:
+ work_order_name = filters.get("work_order")
+ filter_conditions.update(
+ {"work_order_filter": f"AND wo.name='{work_order_name}'"}
+ )
+ return filter_conditions
+
diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js
index d16485e8cc6..695efacb694 100644
--- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js
+++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.js
@@ -6,4 +6,3 @@ frappe.require("assets/erpnext/js/purchase_trends_filters.js", function() {
filters: erpnext.get_purchase_trends_filters()
}
});
-
diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
index 8227f1548c1..0d96ea6aa8e 100644
--- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
+++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
@@ -48,4 +48,4 @@ def get_chart_data(data, filters):
},
"type" : "bar",
"colors":["#5e64ff"]
- }
\ 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
index c3339fd341e..cc3aa3522d8 100644
--- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
@@ -50,4 +50,3 @@ def get_columns(filters):
def get_data(filters):
return get_stock_ledger_entries(filters, '<=', order="asc") or []
-
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.js b/erpnext/stock/report/stock_ageing/stock_ageing.js
index 8495142ba5b..b22788f7a29 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.js
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.js
@@ -64,4 +64,4 @@ frappe.query_reports["Stock Ageing"] = {
"default": 0
}
]
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.js b/erpnext/stock/report/stock_analytics/stock_analytics.js
index 6b384e28611..78afe6d2642 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.js
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.js
@@ -36,12 +36,26 @@ frappe.query_reports["Stock Analytics"] = {
options:"Brand",
default: "",
},
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1,
+ },
{
fieldname: "warehouse",
label: __("Warehouse"),
fieldtype: "Link",
- options:"Warehouse",
+ options: "Warehouse",
default: "",
+ get_query: function() {
+ const company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: { 'company': company }
+ }
+ }
},
{
fieldname: "from_date",
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py
index fde934b1339..a1e1e7fce7c 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.py
@@ -1,14 +1,15 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
+import datetime
-from __future__ import unicode_literals
import frappe
from frappe import _, scrub
-from frappe.utils import getdate, flt
+from frappe.utils import getdate, get_quarter_start, get_first_day_of_week
+from frappe.utils import get_first_day as get_first_day_of_month
+
from erpnext.stock.report.stock_balance.stock_balance import (get_items, get_stock_ledger_entries, get_item_details)
from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.utils import is_reposting_item_valuation_in_progress
-from six import iteritems
def execute(filters=None):
is_reposting_item_valuation_in_progress()
@@ -71,7 +72,8 @@ def get_columns(filters):
def get_period_date_ranges(filters):
from dateutil.relativedelta import relativedelta
- from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
+ from_date = round_down_to_nearest_frequency(filters.from_date, filters.range)
+ to_date = getdate(filters.to_date)
increment = {
"Monthly": 1,
@@ -97,6 +99,31 @@ def get_period_date_ranges(filters):
return periodic_daterange
+
+def round_down_to_nearest_frequency(date: str, frequency: str) -> datetime.datetime:
+ """Rounds down the date to nearest frequency unit.
+ example:
+
+ >>> round_down_to_nearest_frequency("2021-02-21", "Monthly")
+ datetime.datetime(2021, 2, 1)
+
+ >>> round_down_to_nearest_frequency("2021-08-21", "Yearly")
+ datetime.datetime(2021, 1, 1)
+ """
+
+ def _get_first_day_of_fiscal_year(date):
+ fiscal_year = get_fiscal_year(date)
+ return fiscal_year and fiscal_year[1] or date
+
+ round_down_function = {
+ "Monthly": get_first_day_of_month,
+ "Quarterly": get_quarter_start,
+ "Weekly": get_first_day_of_week,
+ "Yearly": _get_first_day_of_fiscal_year,
+ }.get(frequency, getdate)
+ return round_down_function(date)
+
+
def get_period(posting_date, filters):
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
@@ -177,7 +204,7 @@ def get_data(filters):
periodic_data = get_periodic_data(sle, filters)
ranges = get_period_date_ranges(filters)
- for dummy, item_data in iteritems(item_details):
+ for dummy, item_data in item_details.items():
row = {
"name": item_data.name,
"item_name": item_data.item_name,
@@ -208,7 +235,3 @@ def get_chart_data(columns):
chart["type"] = "line"
return chart
-
-
-
-
diff --git a/erpnext/stock/report/stock_analytics/test_stock_analytics.py b/erpnext/stock/report/stock_analytics/test_stock_analytics.py
new file mode 100644
index 00000000000..00e268b4e0e
--- /dev/null
+++ b/erpnext/stock/report/stock_analytics/test_stock_analytics.py
@@ -0,0 +1,35 @@
+import datetime
+import unittest
+
+from frappe import _dict
+from erpnext.accounts.utils import get_fiscal_year
+
+from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges
+
+
+class TestStockAnalyticsReport(unittest.TestCase):
+ def test_get_period_date_ranges(self):
+
+ filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06")
+
+ ranges = get_period_date_ranges(filters)
+
+ expected_ranges = [
+ [datetime.date(2020, 12, 1), datetime.date(2020, 12, 31)],
+ [datetime.date(2021, 1, 1), datetime.date(2021, 1, 31)],
+ [datetime.date(2021, 2, 1), datetime.date(2021, 2, 6)],
+ ]
+
+ self.assertEqual(ranges, expected_ranges)
+
+ def test_get_period_date_ranges_yearly(self):
+
+ filters = _dict(range="Yearly", from_date="2021-01-28", to_date="2021-02-06")
+
+ ranges = get_period_date_ranges(filters)
+ first_date = get_fiscal_year("2021-01-28")[1]
+ expected_ranges = [
+ [first_date, datetime.date(2021, 2, 6)],
+ ]
+
+ self.assertEqual(ranges, expected_ranges)
diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
index bfc4471b9af..7e0c0e8ab35 100644
--- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
+++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
@@ -128,4 +128,4 @@ def get_columns(filters):
"fieldtype": "Currency",
"width": "120"
}
- ]
\ No newline at end of file
+ ]
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 8909f217f49..b6923e97c4f 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -23,6 +23,7 @@ def execute(filters=None):
conversion_factors = []
if opening_row:
data.append(opening_row)
+ conversion_factors.append(0)
actual_qty = stock_value = 0
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 808d2791709..7956f2e8648 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -32,7 +32,7 @@ def execute(filters=None):
if filters.brand and filters.brand != item.brand:
continue
-
+
elif filters.item_group and filters.item_group != item.item_group:
continue
diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py
index 78e95df9898..fa19eeba58b 100644
--- a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py
+++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py
@@ -58,14 +58,14 @@ def get_data(warehouse):
serial_item_list = frappe.get_all("Item", filters={
'has_serial_no': True,
}, fields=['item_code', 'item_name'])
-
+
status_list = ['Active', 'Expired']
data = []
for item in serial_item_list:
- total_serial_no = frappe.db.count("Serial No",
+ total_serial_no = frappe.db.count("Serial No",
filters={"item_code": item.item_code, "status": ("in", status_list), "warehouse": warehouse})
- actual_qty = frappe.db.get_value('Bin', fieldname=['actual_qty'],
+ actual_qty = frappe.db.get_value('Bin', fieldname=['actual_qty'],
filters={"warehouse": warehouse, "item_code": item.item_code})
# frappe.db.get_value returns null if no record exist.
@@ -84,4 +84,4 @@ def get_data(warehouse):
data.append(row)
- return data
\ No newline at end of file
+ return data
diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.js b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.js
index cdc9895917c..5b006470756 100644
--- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.js
+++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.js
@@ -25,4 +25,4 @@ frappe.query_reports["Supplier-Wise Sales Analytics"] = {
"default": frappe.datetime.month_end()
},
]
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.js b/erpnext/stock/report/total_stock_summary/total_stock_summary.js
index 264642856da..90648f1b249 100644
--- a/erpnext/stock/report/total_stock_summary/total_stock_summary.js
+++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.js
@@ -38,4 +38,4 @@ frappe.query_reports["Total Stock Summary"] = {
"reqd": 1
},
]
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 8f9ec465e5d..e98df737cb7 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -326,6 +326,7 @@ class update_entries_after(object):
where
item_code = %(item_code)s
and warehouse = %(warehouse)s
+ and is_cancelled = 0
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) = timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
order by
@@ -948,7 +949,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
return valuation_rate
-def update_qty_in_future_sle(args, allow_negative_stock=None):
+def update_qty_in_future_sle(args, allow_negative_stock=False):
"""Recalculate Qty after Transaction in future SLEs based on current SLE."""
datetime_limit_condition = ""
qty_shift = args.actual_qty
@@ -1037,8 +1038,8 @@ def get_datetime_limit_condition(detail):
)
)"""
-def validate_negative_qty_in_future_sle(args, allow_negative_stock=None):
- allow_negative_stock = allow_negative_stock \
+def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
+ allow_negative_stock = cint(allow_negative_stock) \
or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
if (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation") and not allow_negative_stock:
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index b57b2aa6b8f..9f6d0a8addd 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -224,7 +224,7 @@ def get_avg_purchase_rate(serial_nos):
def get_valuation_method(item_code):
"""get valuation method from item or default"""
- val_method = frappe.db.get_value('Item', item_code, 'valuation_method')
+ val_method = frappe.db.get_value('Item', item_code, 'valuation_method', cache=True)
if not val_method:
val_method = frappe.db.get_value("Stock Settings", None, "valuation_method") or "FIFO"
return val_method
@@ -275,17 +275,17 @@ def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
return valid_serial_nos
def validate_warehouse_company(warehouse, company):
- warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company")
+ warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company", cache=True)
if warehouse_company and warehouse_company != company:
frappe.throw(_("Warehouse {0} does not belong to company {1}").format(warehouse, company),
InvalidWarehouseCompany)
def is_group_warehouse(warehouse):
- if frappe.db.get_value("Warehouse", warehouse, "is_group"):
+ if frappe.db.get_value("Warehouse", warehouse, "is_group", cache=True):
frappe.throw(_("Group node warehouse is not allowed to select for transactions"))
def validate_disabled_warehouse(warehouse):
- if frappe.db.get_value("Warehouse", warehouse, "disabled"):
+ if frappe.db.get_value("Warehouse", warehouse, "disabled", cache=True):
frappe.throw(_("Disabled Warehouse {0} cannot be used for this transaction.").format(get_link_to_form('Warehouse', warehouse)))
def update_included_uom_in_report(columns, result, include_uom, conversion_factors):
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index b48925d33ad..074f1aca0e2 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -222,7 +222,7 @@ class Issue(Document):
}).insert(ignore_permissions=True)
return replicated_issue.name
-
+
def reset_issue_metrics(self):
self.db_set("resolution_time", None)
self.db_set("user_resolution_time", None)
diff --git a/erpnext/support/doctype/issue_priority/issue_priority.py b/erpnext/support/doctype/issue_priority/issue_priority.py
index 7c8925ebc30..514b6cc26ba 100644
--- a/erpnext/support/doctype/issue_priority/issue_priority.py
+++ b/erpnext/support/doctype/issue_priority/issue_priority.py
@@ -8,4 +8,4 @@ from frappe import _
from frappe.model.document import Document
class IssuePriority(Document):
- pass
\ No newline at end of file
+ pass
diff --git a/erpnext/support/doctype/issue_priority/test_issue_priority.py b/erpnext/support/doctype/issue_priority/test_issue_priority.py
index a7b55f8a74c..618c93ea9d8 100644
--- a/erpnext/support/doctype/issue_priority/test_issue_priority.py
+++ b/erpnext/support/doctype/issue_priority/test_issue_priority.py
@@ -25,4 +25,4 @@ def insert_priority(name):
frappe.get_doc({
"doctype": "Issue Priority",
"name": name
- }).insert(ignore_permissions=True)
\ No newline at end of file
+ }).insert(ignore_permissions=True)
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index ec0237e2eac..11812426d67 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -153,7 +153,7 @@ def get_active_service_level_agreement_for(doc):
filters += [["Service Level Agreement", "default_service_level_agreement", "=", 0]]
agreements = frappe.get_all("Service Level Agreement", filters=filters, or_filters=or_filters,
fields=["name", "default_priority", "condition"])
-
+
# check if the current document on which SLA is to be applied fulfills all the conditions
filtered_agreements = []
for agreement in agreements:
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py
index f2bd6813965..7e7a405d6e7 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement_dashboard.py
@@ -9,4 +9,4 @@ def get_data():
'items': ['Issue']
}
]
- }
\ No newline at end of file
+ }
diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.js b/erpnext/support/doctype/warranty_claim/warranty_claim.js
index 79f46758d12..d2ee52ad5cc 100644
--- a/erpnext/support/doctype/warranty_claim/warranty_claim.js
+++ b/erpnext/support/doctype/warranty_claim/warranty_claim.js
@@ -93,4 +93,4 @@ cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) {
]
}
}
-};
\ No newline at end of file
+};
diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py
index 922da2b33de..69bf2730d35 100644
--- a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py
+++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py
@@ -32,4 +32,4 @@ def execute(filters=None):
ORDER BY creation_date desc
''', (filters.from_date, filters.to_date))
- return columns, data
\ No newline at end of file
+ return columns, data
diff --git a/erpnext/support/report/issue_analytics/issue_analytics.py b/erpnext/support/report/issue_analytics/issue_analytics.py
index 3fdb10ddf38..54fce0b3592 100644
--- a/erpnext/support/report/issue_analytics/issue_analytics.py
+++ b/erpnext/support/report/issue_analytics/issue_analytics.py
@@ -218,4 +218,4 @@ class IssueAnalytics(object):
'datasets': []
},
'type': 'line'
- }
\ No newline at end of file
+ }
diff --git a/erpnext/support/report/issue_analytics/test_issue_analytics.py b/erpnext/support/report/issue_analytics/test_issue_analytics.py
index 77483198ecc..a9d961a4592 100644
--- a/erpnext/support/report/issue_analytics/test_issue_analytics.py
+++ b/erpnext/support/report/issue_analytics/test_issue_analytics.py
@@ -22,7 +22,7 @@ class TestIssueAnalytics(unittest.TestCase):
if current_month_date.year != last_month_date.year:
self.current_month += '_' + str(current_month_date.year)
self.last_month += '_' + str(last_month_date.year)
-
+
def test_issue_analytics(self):
create_service_level_agreements_for_issues()
create_issue_types()
@@ -211,4 +211,4 @@ def create_records():
"assign_to": ["test@example.com", "test1@example.com"],
"doctype": "Issue",
"name": issue.name
- })
\ No newline at end of file
+ })
diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py
index bba25b8bed6..7c4af39f104 100644
--- a/erpnext/support/report/issue_summary/issue_summary.py
+++ b/erpnext/support/report/issue_summary/issue_summary.py
@@ -362,4 +362,3 @@ class IssueSummary(object):
'datatype': 'Int',
}
]
-
diff --git a/erpnext/support/web_form/issues/issues.js b/erpnext/support/web_form/issues/issues.js
index 699703c5792..ffc5e984253 100644
--- a/erpnext/support/web_form/issues/issues.js
+++ b/erpnext/support/web_form/issues/issues.js
@@ -1,3 +1,3 @@
frappe.ready(function() {
// bind events here
-})
\ No newline at end of file
+})
diff --git a/erpnext/telephony/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py
index c00dfa90566..6f8e4116956 100644
--- a/erpnext/telephony/doctype/call_log/call_log.py
+++ b/erpnext/telephony/doctype/call_log/call_log.py
@@ -173,4 +173,3 @@ def get_linked_call_logs(doctype, docname):
})
return timeline_contents
-
diff --git a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js
index 1bcc8461323..b80acdb3760 100644
--- a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js
+++ b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js
@@ -99,4 +99,3 @@ frappe.ui.form.on('Incoming Call Settings', {
validate_call_schedule(frm.doc.call_handling_schedule);
}
});
-
diff --git a/erpnext/templates/emails/anniversary_reminder.html b/erpnext/templates/emails/anniversary_reminder.html
new file mode 100644
index 00000000000..ac9f7e4993a
--- /dev/null
+++ b/erpnext/templates/emails/anniversary_reminder.html
@@ -0,0 +1,25 @@
+
+
+ {% for person in anniversary_persons %}
+ {% if person.image %}
+
+
+ {% else %}
+
+ {{ frappe.utils.get_abbr(person.name) }}
+
+ {% endif %}
+ {% endfor %}
+
+
+ {{ reminder_text }}
+
{{ message }}
+
+
\ No newline at end of file
diff --git a/erpnext/templates/emails/birthday_reminder.html b/erpnext/templates/emails/birthday_reminder.html
index 12cdf1ec600..1f57b4969c0 100644
--- a/erpnext/templates/emails/birthday_reminder.html
+++ b/erpnext/templates/emails/birthday_reminder.html
@@ -22,4 +22,4 @@
{{ reminder_text }}
{{ message }}
-
\ No newline at end of file
+
diff --git a/erpnext/templates/emails/daily_project_summary.html b/erpnext/templates/emails/daily_project_summary.html
index 8b60830db62..5ccc6101665 100644
--- a/erpnext/templates/emails/daily_project_summary.html
+++ b/erpnext/templates/emails/daily_project_summary.html
@@ -43,4 +43,4 @@
-{% endfor %}
\ No newline at end of file
+{% endfor %}
diff --git a/erpnext/templates/emails/daily_work_summary.html b/erpnext/templates/emails/daily_work_summary.html
index a22e09cb8de..1764e8f7038 100644
--- a/erpnext/templates/emails/daily_work_summary.html
+++ b/erpnext/templates/emails/daily_work_summary.html
@@ -52,4 +52,4 @@
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/erpnext/templates/emails/holiday_reminder.html b/erpnext/templates/emails/holiday_reminder.html
new file mode 100644
index 00000000000..e38d27bf8bc
--- /dev/null
+++ b/erpnext/templates/emails/holiday_reminder.html
@@ -0,0 +1,16 @@
+
+ {{ reminder_text }}
+
{{ message }}
+
+
+{% if advance_holiday_reminder %}
+ {% if holidays | len > 0 %}
+
+ {% for holiday in holidays %}
+