diff --git a/.eslintrc b/.eslintrc
index 12fefa0968c..f3d4fd5091f 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -2,65 +2,32 @@
"env": {
"browser": true,
"node": true,
- "es6": true
+ "es2022": true
},
"parserOptions": {
- "ecmaVersion": 11,
"sourceType": "module"
},
"extends": "eslint:recommended",
"rules": {
- "indent": [
- "error",
- "tab",
- { "SwitchCase": 1 }
- ],
- "brace-style": [
- "error",
- "1tbs"
- ],
- "space-unary-ops": [
- "error",
- { "words": true }
- ],
- "linebreak-style": [
- "error",
- "unix"
- ],
- "quotes": [
- "off"
- ],
- "semi": [
- "warn",
- "always"
- ],
- "camelcase": [
- "off"
- ],
- "no-unused-vars": [
- "warn"
- ],
- "no-redeclare": [
- "warn"
- ],
- "no-console": [
- "warn"
- ],
- "no-extra-boolean-cast": [
- "off"
- ],
- "no-control-regex": [
- "off"
- ],
- "space-before-blocks": "warn",
- "keyword-spacing": "warn",
- "comma-spacing": "warn",
- "key-spacing": "warn"
+ "indent": "off",
+ "brace-style": "off",
+ "no-mixed-spaces-and-tabs": "off",
+ "no-useless-escape": "off",
+ "space-unary-ops": ["error", { "words": true }],
+ "linebreak-style": "off",
+ "quotes": ["off"],
+ "semi": "off",
+ "camelcase": "off",
+ "no-unused-vars": "off",
+ "no-console": ["warn"],
+ "no-extra-boolean-cast": ["off"],
+ "no-control-regex": ["off"]
},
"root": true,
"globals": {
"frappe": true,
"Vue": true,
+ "SetVueGlobals": true,
"erpnext": true,
"hub": true,
"$": true,
@@ -97,8 +64,10 @@
"is_null": true,
"in_list": true,
"has_common": true,
+ "posthog": true,
"has_words": true,
"validate_email": true,
+ "open_web_template_values_editor": true,
"get_number_format": true,
"format_number": true,
"format_currency": true,
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index af6d8f26a73..94b76b12ce7 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -9,21 +9,22 @@ jobs:
name: linters
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python 3.10
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: '3.10'
+ cache: pip
- name: Install and Run Pre-commit
- uses: pre-commit/action@v2.0.3
+ uses: pre-commit/action@v3.0.0
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep
- run: pip install semgrep==0.97.0
+ run: pip install semgrep
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index 9b4db49d084..2ce1125456e 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -7,11 +7,9 @@ on:
- '**.css'
- '**.md'
- '**.html'
- push:
- branches: [ develop ]
- paths-ignore:
- - '**.js'
- - '**.md'
+ schedule:
+ # Run everday at midnight UTC / 5:30 IST
+ - cron: "0 0 * * *"
workflow_dispatch:
inputs:
user:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d70977c07e2..2c9a60c7c4c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,8 +16,26 @@ repos:
- id: check-merge-conflict
- id: check-ast
+ - repo: https://github.com/pre-commit/mirrors-eslint
+ rev: v8.44.0
+ hooks:
+ - id: eslint
+ types_or: [javascript]
+ args: ['--quiet']
+ # Ignore any files that might contain jinja / bundles
+ exclude: |
+ (?x)^(
+ erpnext/public/dist/.*|
+ cypress/.*|
+ .*node_modules.*|
+ .*boilerplate.*|
+ erpnext/public/js/controllers/.*|
+ erpnext/templates/pages/order.js|
+ erpnext/templates/includes/.*
+ )$
+
- repo: https://github.com/PyCQA/flake8
- rev: 5.0.4
+ rev: 6.0.0
hooks:
- id: flake8
additional_dependencies: [
diff --git a/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json
index 8631d3dc2a3..4883106227b 100644
--- a/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json
+++ b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json
@@ -4,18 +4,19 @@
"creation": "2020-07-17 11:25:34.593061",
"docstatus": 0,
"doctype": "Dashboard Chart",
- "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
"filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
- "modified": "2020-07-22 12:24:49.144210",
+ "modified": "2023-07-19 13:13:13.307073",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Budget Variance",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Budget Variance Report",
+ "roles": [],
"timeseries": 0,
"type": "Bar",
"use_report_chart": 1,
diff --git a/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json b/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json
index 3fa995bbe15..25caa44769b 100644
--- a/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json
+++ b/erpnext/accounts/dashboard_chart/profit_and_loss/profit_and_loss.json
@@ -4,18 +4,19 @@
"creation": "2020-07-17 11:25:34.448572",
"docstatus": 0,
"doctype": "Dashboard Chart",
- "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
"filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
- "modified": "2020-07-22 12:33:48.888943",
+ "modified": "2023-07-19 13:08:56.470390",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Profit and Loss",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Profit and Loss Statement",
+ "roles": [],
"timeseries": 0,
"type": "Bar",
"use_report_chart": 1,
diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py
index 9540084e09f..e75af7047f1 100644
--- a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py
+++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py
@@ -14,10 +14,8 @@ class AccountClosingBalance(Document):
pass
-def make_closing_entries(closing_entries, voucher_name):
+def make_closing_entries(closing_entries, voucher_name, company, closing_date):
accounting_dimensions = get_accounting_dimensions()
- company = closing_entries[0].get("company")
- closing_date = closing_entries[0].get("closing_date")
previous_closing_entries = get_previous_closing_entries(
company, closing_date, accounting_dimensions
diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.js b/erpnext/accounts/doctype/accounting_period/accounting_period.js
index e3d805a1681..f17b6f9c695 100644
--- a/erpnext/accounts/doctype/accounting_period/accounting_period.js
+++ b/erpnext/accounts/doctype/accounting_period/accounting_period.js
@@ -20,5 +20,11 @@ frappe.ui.form.on('Accounting Period', {
}
});
}
+
+ frm.set_query("document_type", "closed_documents", () => {
+ return {
+ query: "erpnext.controllers.queries.get_doctypes_for_closing",
+ }
+ });
}
});
diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py
index 80c9715e8e1..d5f37a68067 100644
--- a/erpnext/accounts/doctype/accounting_period/accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py
@@ -11,6 +11,10 @@ class OverlapError(frappe.ValidationError):
pass
+class ClosedAccountingPeriod(frappe.ValidationError):
+ pass
+
+
class AccountingPeriod(Document):
def validate(self):
self.validate_overlap()
@@ -65,3 +69,42 @@ class AccountingPeriod(Document):
"closed_documents",
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
)
+
+
+def validate_accounting_period_on_doc_save(doc, method=None):
+ if doc.doctype == "Bank Clearance":
+ return
+ elif doc.doctype == "Asset":
+ if doc.is_existing_asset:
+ return
+ else:
+ date = doc.available_for_use_date
+ elif doc.doctype == "Asset Repair":
+ date = doc.completion_date
+ else:
+ date = doc.posting_date
+
+ ap = frappe.qb.DocType("Accounting Period")
+ cd = frappe.qb.DocType("Closed Document")
+
+ accounting_period = (
+ frappe.qb.from_(ap)
+ .from_(cd)
+ .select(ap.name)
+ .where(
+ (ap.name == cd.parent)
+ & (ap.company == doc.company)
+ & (cd.closed == 1)
+ & (cd.document_type == doc.doctype)
+ & (date >= ap.start_date)
+ & (date <= ap.end_date)
+ )
+ ).run(as_dict=1)
+
+ if accounting_period:
+ frappe.throw(
+ _("You cannot create a {0} within the closed Accounting Period {1}").format(
+ doc.doctype, frappe.bold(accounting_period[0]["name"])
+ ),
+ ClosedAccountingPeriod,
+ )
diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
index 85025d190f5..41d94797ad6 100644
--- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
@@ -6,9 +6,11 @@ import unittest
import frappe
from frappe.utils import add_months, nowdate
-from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
+from erpnext.accounts.doctype.accounting_period.accounting_period import (
+ ClosedAccountingPeriod,
+ OverlapError,
+)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
-from erpnext.accounts.general_ledger import ClosedAccountingPeriod
test_dependencies = ["Item"]
@@ -33,9 +35,9 @@ class TestAccountingPeriod(unittest.TestCase):
ap1.save()
doc = create_sales_invoice(
- do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
+ do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
)
- self.assertRaises(ClosedAccountingPeriod, doc.submit)
+ self.assertRaises(ClosedAccountingPeriod, doc.save)
def tearDown(self):
for d in frappe.get_all("Accounting Period"):
diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index 6667193a54c..83bd7fe8624 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -102,7 +102,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
}
onScriptLoaded(me) {
- me.linkHandler = Plaid.create({
+ me.linkHandler = Plaid.create({ // eslint-disable-line no-undef
env: me.plaid_env,
token: me.token,
onSuccess: me.plaid_success
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js
index 632fab0197c..c427cc8b87c 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.js
+++ b/erpnext/accounts/doctype/cost_center/cost_center.js
@@ -70,7 +70,7 @@ frappe.ui.form.on('Cost Center', {
}
],
primary_action: function() {
- var data = d.get_values();
+ let data = d.get_values();
if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) {
d.hide();
return;
@@ -91,8 +91,8 @@ frappe.ui.form.on('Cost Center', {
if(r.message) {
frappe.set_route("Form", "Cost Center", r.message);
} else {
- me.frm.set_value("cost_center_name", data.cost_center_name);
- me.frm.set_value("cost_center_number", data.cost_center_number);
+ frm.set_value("cost_center_name", data.cost_center_name);
+ frm.set_value("cost_center_number", data.cost_center_number);
}
d.hide();
}
diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js
index 9909c6c2ab0..1ac909e7451 100644
--- a/erpnext/accounts/doctype/dunning/dunning.js
+++ b/erpnext/accounts/doctype/dunning/dunning.js
@@ -1,13 +1,14 @@
-// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on("Dunning", {
setup: function (frm) {
- frm.set_query("sales_invoice", () => {
+ frm.set_query("sales_invoice", "overdue_payments", () => {
return {
filters: {
docstatus: 1,
company: frm.doc.company,
+ customer: frm.doc.customer,
outstanding_amount: [">", 0],
status: "Overdue"
},
@@ -22,14 +23,24 @@ frappe.ui.form.on("Dunning", {
}
};
});
+ frm.set_query("cost_center", () => {
+ return {
+ filters: {
+ company: frm.doc.company,
+ is_group: 0
+ }
+ };
+ });
+
+ frm.set_query("contact_person", erpnext.queries.contact_query);
+ frm.set_query("customer_address", erpnext.queries.address_query);
+ frm.set_query("company_address", erpnext.queries.company_address_query);
+
+ // cannot add rows manually, only via button "Fetch Overdue Payments"
+ frm.set_df_property("overdue_payments", "cannot_add_rows", true);
},
refresh: function (frm) {
frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1);
- frm.set_df_property(
- "sales_invoice",
- "read_only",
- frm.doc.__islocal ? 0 : 1
- );
if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") {
frm.add_custom_button(__("Resolve"), () => {
frm.set_value("status", "Resolved");
@@ -40,42 +51,111 @@ frappe.ui.form.on("Dunning", {
__("Payment"),
function () {
frm.events.make_payment_entry(frm);
- },__("Create")
+ }, __("Create")
);
frm.page.set_inner_btn_group_as_primary(__("Create"));
}
- if(frm.doc.docstatus > 0) {
- frm.add_custom_button(__('Ledger'), function() {
- frappe.route_options = {
- "voucher_no": frm.doc.name,
- "from_date": frm.doc.posting_date,
- "to_date": frm.doc.posting_date,
- "company": frm.doc.company,
- "show_cancelled_entries": frm.doc.docstatus === 2
- };
- frappe.set_route("query-report", "General Ledger");
- }, __('View'));
+ if (frm.doc.docstatus === 0) {
+ frm.add_custom_button(__("Fetch Overdue Payments"), () => {
+ erpnext.utils.map_current_doc({
+ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
+ source_doctype: "Sales Invoice",
+ date_field: "due_date",
+ target: frm,
+ setters: {
+ customer: frm.doc.customer || undefined,
+ },
+ get_query_filters: {
+ docstatus: 1,
+ status: "Overdue",
+ company: frm.doc.company
+ },
+ allow_child_item_selection: true,
+ child_fieldname: "payment_schedule",
+ child_columns: ["due_date", "outstanding"],
+ });
+ });
}
+
+ frappe.dynamic_link = { doc: frm.doc, fieldname: 'customer', doctype: 'Customer' };
+
+ frm.toggle_display("customer_name", (frm.doc.customer_name && frm.doc.customer_name !== frm.doc.customer));
},
- overdue_days: function (frm) {
- frappe.db.get_value(
- "Dunning Type",
- {
- start_day: ["<", frm.doc.overdue_days],
- end_day: [">=", frm.doc.overdue_days],
- },
- "dunning_type",
- (r) => {
- if (r) {
- frm.set_value("dunning_type", r.dunning_type);
- } else {
- frm.set_value("dunning_type", "");
- frm.set_value("rate_of_interest", "");
- frm.set_value("dunning_fee", "");
+ // When multiple companies are set up. in case company name is changed set default company address
+ company: function (frm) {
+ if (frm.doc.company) {
+ frappe.call({
+ method: "erpnext.setup.doctype.company.company.get_default_company_address",
+ args: { name: frm.doc.company, existing_address: frm.doc.company_address || "" },
+ debounce: 2000,
+ callback: function (r) {
+ frm.set_value("company_address", r && r.message || "");
+ }
+ });
+
+ if (frm.fields_dict.currency) {
+ const company_currency = erpnext.get_currency(frm.doc.company);
+
+ if (!frm.doc.currency) {
+ frm.set_value("currency", company_currency);
+ }
+
+ if (frm.doc.currency == company_currency) {
+ frm.set_value("conversion_rate", 1.0);
}
}
- );
+
+ const company_doc = frappe.get_doc(":Company", frm.doc.company);
+ if (company_doc.default_letter_head) {
+ if (frm.fields_dict.letter_head) {
+ frm.set_value("letter_head", company_doc.default_letter_head);
+ }
+ }
+ }
+ },
+ currency: function (frm) {
+ // this.set_dynamic_labels();
+ const company_currency = erpnext.get_currency(frm.doc.company);
+ // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
+ if (frm.doc.currency && frm.doc.currency !== company_currency) {
+ frappe.call({
+ method: "erpnext.setup.utils.get_exchange_rate",
+ args: {
+ transaction_date: frm.doc.posting_date,
+ from_currency: frm.doc.currency,
+ to_currency: company_currency,
+ args: "for_selling"
+ },
+ freeze: true,
+ freeze_message: __("Fetching exchange rates ..."),
+ callback: function(r) {
+ const exchange_rate = flt(r.message);
+ if (exchange_rate != frm.doc.conversion_rate) {
+ frm.set_value("conversion_rate", exchange_rate);
+ }
+ }
+ });
+ } else {
+ frm.trigger("conversion_rate");
+ }
+ },
+ customer: (frm) => {
+ erpnext.utils.get_party_details(frm);
+ },
+ conversion_rate: function (frm) {
+ if (frm.doc.currency === erpnext.get_currency(frm.doc.company)) {
+ frm.set_value("conversion_rate", 1.0);
+ }
+
+ // Make read only if Accounts Settings doesn't allow stale rates
+ frm.set_df_property("conversion_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
+ },
+ customer_address: function (frm) {
+ erpnext.utils.get_address_display(frm, "customer_address");
+ },
+ company_address: function (frm) {
+ erpnext.utils.get_address_display(frm, "company_address");
},
dunning_type: function (frm) {
frm.trigger("get_dunning_letter_text");
@@ -87,7 +167,7 @@ frappe.ui.form.on("Dunning", {
if (frm.doc.dunning_type) {
frappe.call({
method:
- "erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text",
+ "erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text",
args: {
dunning_type: frm.doc.dunning_type,
language: frm.doc.language,
@@ -106,49 +186,62 @@ frappe.ui.form.on("Dunning", {
});
}
},
- due_date: function (frm) {
- frm.trigger("calculate_overdue_days");
- },
posting_date: function (frm) {
frm.trigger("calculate_overdue_days");
},
rate_of_interest: function (frm) {
- frm.trigger("calculate_interest_and_amount");
- },
- outstanding_amount: function (frm) {
- frm.trigger("calculate_interest_and_amount");
- },
- interest_amount: function (frm) {
- frm.trigger("calculate_interest_and_amount");
+ frm.trigger("calculate_interest");
},
dunning_fee: function (frm) {
- frm.trigger("calculate_interest_and_amount");
+ frm.trigger("calculate_totals");
},
- sales_invoice: function (frm) {
- frm.trigger("calculate_overdue_days");
+ overdue_payments_add: function (frm) {
+ frm.trigger("calculate_totals");
+ },
+ overdue_payments_remove: function (frm) {
+ frm.trigger("calculate_totals");
},
calculate_overdue_days: function (frm) {
- if (frm.doc.posting_date && frm.doc.due_date) {
- const overdue_days = moment(frm.doc.posting_date).diff(
- frm.doc.due_date,
- "days"
- );
- frm.set_value("overdue_days", overdue_days);
- }
+ frm.doc.overdue_payments.forEach((row) => {
+ if (frm.doc.posting_date && row.due_date) {
+ const overdue_days = moment(frm.doc.posting_date).diff(
+ row.due_date,
+ "days"
+ );
+ frappe.model.set_value(row.doctype, row.name, "overdue_days", overdue_days);
+ }
+ });
},
- calculate_interest_and_amount: function (frm) {
- const interest_per_year = frm.doc.outstanding_amount * frm.doc.rate_of_interest / 100;
- const interest_amount = flt((interest_per_year * cint(frm.doc.overdue_days)) / 365 || 0, precision('interest_amount'));
- const dunning_amount = flt(interest_amount + frm.doc.dunning_fee, precision('dunning_amount'));
- const grand_total = flt(frm.doc.outstanding_amount + dunning_amount, precision('grand_total'));
- frm.set_value("interest_amount", interest_amount);
- frm.set_value("dunning_amount", dunning_amount);
- frm.set_value("grand_total", grand_total);
+ calculate_interest: function (frm) {
+ frm.doc.overdue_payments.forEach((row) => {
+ const interest_per_day = frm.doc.rate_of_interest / 100 / 365;
+ const interest = flt((interest_per_day * row.overdue_days * row.outstanding), precision("interest", row));
+ frappe.model.set_value(row.doctype, row.name, "interest", interest);
+ });
+ },
+ calculate_totals: function (frm) {
+ const total_interest = frm.doc.overdue_payments
+ .reduce((prev, cur) => prev + cur.interest, 0);
+ const total_outstanding = frm.doc.overdue_payments
+ .reduce((prev, cur) => prev + cur.outstanding, 0);
+ const dunning_amount = total_interest + frm.doc.dunning_fee;
+ const base_dunning_amount = dunning_amount * frm.doc.conversion_rate;
+ const grand_total = total_outstanding + dunning_amount;
+
+ function setWithPrecison(field, value) {
+ frm.set_value(field, flt(value, precision(field)));
+ }
+
+ setWithPrecison("total_outstanding", total_outstanding);
+ setWithPrecison("total_interest", total_interest);
+ setWithPrecison("dunning_amount", dunning_amount);
+ setWithPrecison("base_dunning_amount", base_dunning_amount);
+ setWithPrecison("grand_total", grand_total);
},
make_payment_entry: function (frm) {
return frappe.call({
method:
- "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry",
+ "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry",
args: {
dt: frm.doc.doctype,
dn: frm.doc.name,
@@ -160,3 +253,9 @@ frappe.ui.form.on("Dunning", {
});
},
});
+
+frappe.ui.form.on("Overdue Payment", {
+ interest: function (frm) {
+ frm.trigger("calculate_totals");
+ }
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json
index 2a32b99f428..b7e8aeaaafd 100644
--- a/erpnext/accounts/doctype/dunning/dunning.json
+++ b/erpnext/accounts/doctype/dunning/dunning.json
@@ -2,49 +2,60 @@
"actions": [],
"allow_events_in_timeline": 1,
"autoname": "naming_series:",
+ "beta": 1,
"creation": "2019-07-05 16:34:31.013238",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
- "title",
"naming_series",
- "sales_invoice",
"customer",
"customer_name",
- "outstanding_amount",
- "currency",
- "conversion_rate",
"column_break_3",
"company",
"posting_date",
"posting_time",
- "due_date",
- "overdue_days",
+ "status",
+ "section_break_9",
+ "currency",
+ "column_break_11",
+ "conversion_rate",
"address_and_contact_section",
+ "customer_address",
"address_display",
+ "contact_person",
"contact_display",
+ "column_break_16",
+ "company_address",
+ "company_address_display",
"contact_mobile",
"contact_email",
- "column_break_18",
- "company_address_display",
"section_break_6",
"dunning_type",
- "dunning_fee",
"column_break_8",
"rate_of_interest",
- "interest_amount",
"section_break_12",
- "dunning_amount",
- "grand_total",
- "income_account",
+ "overdue_payments",
+ "section_break_28",
+ "total_interest",
+ "dunning_fee",
"column_break_17",
- "status",
- "printing_setting_section",
+ "dunning_amount",
+ "base_dunning_amount",
+ "section_break_32",
+ "spacer",
+ "column_break_33",
+ "total_outstanding",
+ "grand_total",
+ "printing_settings_section",
"language",
"body_text",
"column_break_22",
"letter_head",
"closing_text",
+ "accounting_details_section",
+ "income_account",
+ "column_break_48",
+ "cost_center",
"amended_from"
],
"fields": [
@@ -60,32 +71,17 @@
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
- "options": "DUNN-.MM.-.YY.-"
+ "options": "DUNN-.MM.-.YY.-",
+ "print_hide": 1
},
{
- "fieldname": "sales_invoice",
- "fieldtype": "Link",
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Sales Invoice",
- "options": "Sales Invoice",
- "reqd": 1
- },
- {
- "fetch_from": "sales_invoice.customer_name",
+ "fetch_from": "customer.customer_name",
"fieldname": "customer_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Customer Name",
"read_only": 1
},
- {
- "fetch_from": "sales_invoice.outstanding_amount",
- "fieldname": "outstanding_amount",
- "fieldtype": "Currency",
- "label": "Outstanding Amount",
- "read_only": 1
- },
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
@@ -94,13 +90,8 @@
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
- "label": "Date"
- },
- {
- "fieldname": "overdue_days",
- "fieldtype": "Int",
- "label": "Overdue Days",
- "read_only": 1
+ "label": "Date",
+ "reqd": 1
},
{
"fieldname": "section_break_6",
@@ -112,16 +103,7 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Dunning Type",
- "options": "Dunning Type",
- "reqd": 1
- },
- {
- "default": "0",
- "fieldname": "interest_amount",
- "fieldtype": "Currency",
- "label": "Interest Amount",
- "precision": "2",
- "read_only": 1
+ "options": "Dunning Type"
},
{
"fieldname": "column_break_8",
@@ -134,6 +116,7 @@
"fieldname": "dunning_fee",
"fieldtype": "Currency",
"label": "Dunning Fee",
+ "options": "currency",
"precision": "2"
},
{
@@ -144,36 +127,24 @@
"fieldname": "column_break_17",
"fieldtype": "Column Break"
},
- {
- "fieldname": "printing_setting_section",
- "fieldtype": "Section Break",
- "label": "Printing Setting"
- },
{
"fieldname": "language",
"fieldtype": "Link",
"label": "Print Language",
- "options": "Language"
+ "options": "Language",
+ "print_hide": 1
},
{
"fieldname": "letter_head",
"fieldtype": "Link",
"label": "Letter Head",
- "options": "Letter Head"
+ "options": "Letter Head",
+ "print_hide": 1
},
{
"fieldname": "column_break_22",
"fieldtype": "Column Break"
},
- {
- "fetch_from": "sales_invoice.currency",
- "fieldname": "currency",
- "fieldtype": "Link",
- "hidden": 1,
- "label": "Currency",
- "options": "Currency",
- "read_only": 1
- },
{
"fieldname": "amended_from",
"fieldtype": "Link",
@@ -183,14 +154,6 @@
"print_hide": 1,
"read_only": 1
},
- {
- "allow_on_submit": 1,
- "default": "{customer_name}",
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Title"
- },
{
"fieldname": "body_text",
"fieldtype": "Text Editor",
@@ -201,13 +164,6 @@
"fieldtype": "Text Editor",
"label": "Closing Text"
},
- {
- "fetch_from": "sales_invoice.due_date",
- "fieldname": "due_date",
- "fieldtype": "Date",
- "label": "Due Date",
- "read_only": 1
- },
{
"fieldname": "posting_time",
"fieldtype": "Time",
@@ -222,26 +178,24 @@
"label": "Rate of Interest (%) Yearly"
},
{
+ "collapsible": 1,
"fieldname": "address_and_contact_section",
"fieldtype": "Section Break",
"label": "Address and Contact"
},
{
- "fetch_from": "sales_invoice.address_display",
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
"read_only": 1
},
{
- "fetch_from": "sales_invoice.contact_display",
"fieldname": "contact_display",
"fieldtype": "Small Text",
"label": "Contact",
"read_only": 1
},
{
- "fetch_from": "sales_invoice.contact_mobile",
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
@@ -249,18 +203,12 @@
"read_only": 1
},
{
- "fieldname": "column_break_18",
- "fieldtype": "Column Break"
- },
- {
- "fetch_from": "sales_invoice.company_address_display",
"fieldname": "company_address_display",
"fieldtype": "Small Text",
- "label": "Company Address",
+ "label": "Company Address Display",
"read_only": 1
},
{
- "fetch_from": "sales_invoice.contact_email",
"fieldname": "contact_email",
"fieldtype": "Data",
"label": "Contact Email",
@@ -268,18 +216,18 @@
"read_only": 1
},
{
- "fetch_from": "sales_invoice.customer",
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"options": "Customer",
- "read_only": 1
+ "reqd": 1
},
{
"default": "0",
"fieldname": "grand_total",
"fieldtype": "Currency",
"label": "Grand Total",
+ "options": "currency",
"precision": "2",
"read_only": 1
},
@@ -290,33 +238,150 @@
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Status",
- "options": "Draft\nResolved\nUnresolved\nCancelled"
- },
- {
- "fieldname": "dunning_amount",
- "fieldtype": "Currency",
- "hidden": 1,
- "label": "Dunning Amount",
+ "options": "Draft\nResolved\nUnresolved\nCancelled",
"read_only": 1
},
{
+ "description": "For dunning fee and interest",
+ "fetch_from": "dunning_type.income_account",
"fieldname": "income_account",
"fieldtype": "Link",
"label": "Income Account",
- "options": "Account"
+ "options": "Account",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "overdue_payments",
+ "fieldtype": "Table",
+ "label": "Overdue Payments",
+ "options": "Overdue Payment"
+ },
+ {
+ "fieldname": "section_break_28",
+ "fieldtype": "Section Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "total_interest",
+ "fieldtype": "Currency",
+ "label": "Total Interest",
+ "options": "currency",
+ "precision": "2",
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_outstanding",
+ "fieldtype": "Currency",
+ "label": "Total Outstanding",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "customer_address",
+ "fieldtype": "Link",
+ "label": "Customer Address",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "contact_person",
+ "fieldtype": "Link",
+ "label": "Contact Person",
+ "options": "Contact",
+ "print_hide": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "dunning_amount",
+ "fieldtype": "Currency",
+ "label": "Dunning Amount",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "accounting_details_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Details"
+ },
+ {
+ "fetch_from": "dunning_type.cost_center",
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center",
+ "print_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "printing_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Printing Settings"
+ },
+ {
+ "fieldname": "section_break_32",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_33",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "spacer",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Spacer",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1
+ },
+ {
+ "fieldname": "column_break_16",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "company_address",
+ "fieldtype": "Link",
+ "label": "Company Address",
+ "options": "Address",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break",
+ "label": "Currency"
+ },
+ {
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency"
+ },
+ {
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
},
{
- "fetch_from": "sales_invoice.conversion_rate",
"fieldname": "conversion_rate",
"fieldtype": "Float",
- "hidden": 1,
- "label": "Conversion Rate",
+ "label": "Conversion Rate"
+ },
+ {
+ "default": "0",
+ "fieldname": "base_dunning_amount",
+ "fieldtype": "Currency",
+ "label": "Dunning Amount (Company Currency)",
+ "options": "Company:company:default_currency",
"read_only": 1
+ },
+ {
+ "fieldname": "column_break_48",
+ "fieldtype": "Column Break"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2023-06-03 16:24:01.677026",
+ "modified": "2023-06-15 15:46:53.865712",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Dunning",
diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
index b4df0a5270c..9d0d36b970a 100644
--- a/erpnext/accounts/doctype/dunning/dunning.py
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -1,131 +1,150 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
+"""
+# Accounting
+1. Payment of outstanding invoices with dunning amount
+ - Debit full amount to bank
+ - Credit invoiced amount to receivables
+ - Credit dunning amount to interest and similar revenue
+
+ -> Resolves dunning automatically
+"""
import json
import frappe
-from frappe.utils import cint, flt, getdate
+from frappe import _
+from frappe.contacts.doctype.address.address import get_address_display
+from frappe.utils import getdate
-from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
- get_accounting_dimensions,
-)
-from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
from erpnext.controllers.accounts_controller import AccountsController
class Dunning(AccountsController):
def validate(self):
- self.validate_overdue_days()
- self.validate_amount()
- if not self.income_account:
- self.income_account = frappe.get_cached_value("Company", self.company, "default_income_account")
+ self.validate_same_currency()
+ self.validate_overdue_payments()
+ self.validate_totals()
+ self.set_party_details()
+ self.set_dunning_level()
- def validate_overdue_days(self):
- self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0
+ def validate_same_currency(self):
+ """
+ Throw an error if invoice currency differs from dunning currency.
+ """
+ for row in self.overdue_payments:
+ invoice_currency = frappe.get_value("Sales Invoice", row.sales_invoice, "currency")
+ if invoice_currency != self.currency:
+ frappe.throw(
+ _(
+ "The currency of invoice {} ({}) is different from the currency of this dunning ({})."
+ ).format(row.sales_invoice, invoice_currency, self.currency)
+ )
- def validate_amount(self):
- amounts = calculate_interest_and_amount(
- self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days
+ def validate_overdue_payments(self):
+ daily_interest = self.rate_of_interest / 100 / 365
+
+ for row in self.overdue_payments:
+ row.overdue_days = (getdate(self.posting_date) - getdate(row.due_date)).days or 0
+ row.interest = row.outstanding * daily_interest * row.overdue_days
+
+ def validate_totals(self):
+ self.total_outstanding = sum(row.outstanding for row in self.overdue_payments)
+ self.total_interest = sum(row.interest for row in self.overdue_payments)
+ self.dunning_amount = self.total_interest + self.dunning_fee
+ self.base_dunning_amount = self.dunning_amount * self.conversion_rate
+ self.grand_total = self.total_outstanding + self.dunning_amount
+
+ def set_party_details(self):
+ from erpnext.accounts.party import _get_party_details
+
+ party_details = _get_party_details(
+ self.customer,
+ ignore_permissions=self.flags.ignore_permissions,
+ doctype=self.doctype,
+ company=self.company,
+ posting_date=self.get("posting_date"),
+ fetch_payment_terms_template=False,
+ party_address=self.customer_address,
+ company_address=self.get("company_address"),
)
- if self.interest_amount != amounts.get("interest_amount"):
- self.interest_amount = flt(amounts.get("interest_amount"), self.precision("interest_amount"))
- if self.dunning_amount != amounts.get("dunning_amount"):
- self.dunning_amount = flt(amounts.get("dunning_amount"), self.precision("dunning_amount"))
- if self.grand_total != amounts.get("grand_total"):
- self.grand_total = flt(amounts.get("grand_total"), self.precision("grand_total"))
+ for field in [
+ "customer_address",
+ "address_display",
+ "company_address",
+ "contact_person",
+ "contact_display",
+ "contact_mobile",
+ ]:
+ self.set(field, party_details.get(field))
- def on_submit(self):
- self.make_gl_entries()
+ self.set("company_address_display", get_address_display(self.company_address))
- def on_cancel(self):
- if self.dunning_amount:
- self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
- make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
-
- def make_gl_entries(self):
- if not self.dunning_amount:
- return
- gl_entries = []
- invoice_fields = [
- "project",
- "cost_center",
- "debit_to",
- "party_account_currency",
- "conversion_rate",
- "cost_center",
- ]
- inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1)
-
- accounting_dimensions = get_accounting_dimensions()
- invoice_fields.extend(accounting_dimensions)
-
- dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate)
- default_cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
-
- gl_entries.append(
- self.get_gl_dict(
- {
- "account": inv.debit_to,
- "party_type": "Customer",
- "party": self.customer,
- "due_date": self.due_date,
- "against": self.income_account,
- "debit": dunning_in_company_currency,
- "debit_in_account_currency": self.dunning_amount,
- "against_voucher": self.name,
- "against_voucher_type": "Dunning",
- "cost_center": inv.cost_center or default_cost_center,
- "project": inv.project,
+ def set_dunning_level(self):
+ for row in self.overdue_payments:
+ past_dunnings = frappe.get_all(
+ "Overdue Payment",
+ filters={
+ "payment_schedule": row.payment_schedule,
+ "parent": ("!=", row.parent),
+ "docstatus": 1,
},
- inv.party_account_currency,
- item=inv,
)
- )
- gl_entries.append(
- self.get_gl_dict(
- {
- "account": self.income_account,
- "against": self.customer,
- "credit": dunning_in_company_currency,
- "cost_center": inv.cost_center or default_cost_center,
- "credit_in_account_currency": self.dunning_amount,
- "project": inv.project,
- },
- item=inv,
- )
- )
- make_gl_entries(
- gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False
- )
+ row.dunning_level = len(past_dunnings) + 1
def resolve_dunning(doc, state):
+ """
+ Check if all payments have been made and resolve dunning, if yes. Called
+ when a Payment Entry is submitted.
+ """
for reference in doc.references:
- if reference.reference_doctype == "Sales Invoice" and reference.outstanding_amount <= 0:
- dunnings = frappe.get_list(
- "Dunning",
- filters={"sales_invoice": reference.reference_name, "status": ("!=", "Resolved")},
- ignore_permissions=True,
- )
+ # Consider partial and full payments:
+ # Submitting full payment: outstanding_amount will be 0
+ # Submitting 1st partial payment: outstanding_amount will be the pending installment
+ # Cancelling full payment: outstanding_amount will revert to total amount
+ # Cancelling last partial payment: outstanding_amount will revert to pending amount
+ submit_condition = reference.outstanding_amount < reference.total_amount
+ cancel_condition = reference.outstanding_amount <= reference.total_amount
+
+ if reference.reference_doctype == "Sales Invoice" and (
+ submit_condition if doc.docstatus == 1 else cancel_condition
+ ):
+ state = "Resolved" if doc.docstatus == 2 else "Unresolved"
+ dunnings = get_linked_dunnings_as_per_state(reference.reference_name, state)
for dunning in dunnings:
- frappe.db.set_value("Dunning", dunning.name, "status", "Resolved")
+ resolve = True
+ dunning = frappe.get_doc("Dunning", dunning.get("name"))
+ for overdue_payment in dunning.overdue_payments:
+ outstanding_inv = frappe.get_value(
+ "Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount"
+ )
+ outstanding_ps = frappe.get_value(
+ "Payment Schedule", overdue_payment.payment_schedule, "outstanding"
+ )
+ resolve = False if (outstanding_ps > 0 and outstanding_inv > 0) else True
+
+ dunning.status = "Resolved" if resolve else "Unresolved"
+ dunning.save()
-def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
- interest_amount = 0
- grand_total = flt(outstanding_amount) + flt(dunning_fee)
- if rate_of_interest:
- interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
- interest_amount = (interest_per_year * cint(overdue_days)) / 365
- grand_total += flt(interest_amount)
- dunning_amount = flt(interest_amount) + flt(dunning_fee)
- return {
- "interest_amount": interest_amount,
- "grand_total": grand_total,
- "dunning_amount": dunning_amount,
- }
+def get_linked_dunnings_as_per_state(sales_invoice, state):
+ dunning = frappe.qb.DocType("Dunning")
+ overdue_payment = frappe.qb.DocType("Overdue Payment")
+
+ return (
+ frappe.qb.from_(dunning)
+ .join(overdue_payment)
+ .on(overdue_payment.parent == dunning.name)
+ .select(dunning.name)
+ .where(
+ (dunning.status == state)
+ & (dunning.docstatus != 2)
+ & (overdue_payment.sales_invoice == sales_invoice)
+ )
+ ).run(as_dict=True)
@frappe.whitelist()
diff --git a/erpnext/accounts/doctype/dunning/dunning_dashboard.py b/erpnext/accounts/doctype/dunning/dunning_dashboard.py
deleted file mode 100644
index d1d40314104..00000000000
--- a/erpnext/accounts/doctype/dunning/dunning_dashboard.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from frappe import _
-
-
-def get_data():
- return {
- "fieldname": "dunning",
- "non_standard_fieldnames": {
- "Journal Entry": "reference_name",
- "Payment Entry": "reference_name",
- },
- "transactions": [{"label": _("Payment"), "items": ["Payment Entry", "Journal Entry"]}],
- }
diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py
index e1fd1e984f5..b29ace275f4 100644
--- a/erpnext/accounts/doctype/dunning/test_dunning.py
+++ b/erpnext/accounts/doctype/dunning/test_dunning.py
@@ -1,162 +1,197 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-
-import unittest
-
import frappe
+from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, nowdate, today
-from erpnext.accounts.doctype.dunning.dunning import calculate_interest_and_amount
+from erpnext import get_default_cost_center
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
unlink_payment_on_cancel_of_invoice,
)
+from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
+ create_dunning as create_dunning_from_sales_invoice,
+)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
create_sales_invoice_against_cost_center,
)
+test_dependencies = ["Company", "Cost Center"]
-class TestDunning(unittest.TestCase):
+
+class TestDunning(FrappeTestCase):
@classmethod
- def setUpClass(self):
- create_dunning_type()
- create_dunning_type_with_zero_interest_rate()
+ def setUpClass(cls):
+ super().setUpClass()
+ create_dunning_type("First Notice", fee=0.0, interest=0.0, is_default=1)
+ create_dunning_type("Second Notice", fee=10.0, interest=10.0, is_default=0)
unlink_payment_on_cancel_of_invoice()
@classmethod
- def tearDownClass(self):
+ def tearDownClass(cls):
unlink_payment_on_cancel_of_invoice(0)
+ super().tearDownClass()
- def test_dunning(self):
- dunning = create_dunning()
- amounts = calculate_interest_and_amount(
- dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
- )
- self.assertEqual(round(amounts.get("interest_amount"), 2), 0.44)
- self.assertEqual(round(amounts.get("dunning_amount"), 2), 20.44)
- self.assertEqual(round(amounts.get("grand_total"), 2), 120.44)
+ def test_dunning_without_fees(self):
+ dunning = create_dunning(overdue_days=20)
- def test_dunning_with_zero_interest_rate(self):
- dunning = create_dunning_with_zero_interest_rate()
- amounts = calculate_interest_and_amount(
- dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
- )
- self.assertEqual(round(amounts.get("interest_amount"), 2), 0)
- self.assertEqual(round(amounts.get("dunning_amount"), 2), 20)
- self.assertEqual(round(amounts.get("grand_total"), 2), 120)
+ self.assertEqual(round(dunning.total_outstanding, 2), 100.00)
+ self.assertEqual(round(dunning.total_interest, 2), 0.00)
+ self.assertEqual(round(dunning.dunning_fee, 2), 0.00)
+ self.assertEqual(round(dunning.dunning_amount, 2), 0.00)
+ self.assertEqual(round(dunning.grand_total, 2), 100.00)
- def test_gl_entries(self):
- dunning = create_dunning()
- dunning.submit()
- gl_entries = frappe.db.sql(
- """select account, debit, credit
- from `tabGL Entry` where voucher_type='Dunning' and voucher_no=%s
- order by account asc""",
- dunning.name,
- as_dict=1,
- )
- self.assertTrue(gl_entries)
- expected_values = dict(
- (d[0], d) for d in [["Debtors - _TC", 20.44, 0.0], ["Sales - _TC", 0.0, 20.44]]
- )
- for gle in gl_entries:
- self.assertEqual(expected_values[gle.account][0], gle.account)
- self.assertEqual(expected_values[gle.account][1], gle.debit)
- self.assertEqual(expected_values[gle.account][2], gle.credit)
+ def test_dunning_with_fees_and_interest(self):
+ dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice - _TC")
- def test_payment_entry(self):
- dunning = create_dunning()
+ self.assertEqual(round(dunning.total_outstanding, 2), 100.00)
+ self.assertEqual(round(dunning.total_interest, 2), 0.41)
+ self.assertEqual(round(dunning.dunning_fee, 2), 10.00)
+ self.assertEqual(round(dunning.dunning_amount, 2), 10.41)
+ self.assertEqual(round(dunning.grand_total, 2), 110.41)
+
+ def test_dunning_with_payment_entry(self):
+ dunning = create_dunning(overdue_days=15, dunning_type_name="Second Notice - _TC")
dunning.submit()
pe = get_payment_entry("Dunning", dunning.name)
pe.reference_no = "1"
pe.reference_date = nowdate()
- pe.paid_from_account_currency = dunning.currency
- pe.paid_to_account_currency = dunning.currency
- pe.source_exchange_rate = 1
- pe.target_exchange_rate = 1
pe.insert()
pe.submit()
- si_doc = frappe.get_doc("Sales Invoice", dunning.sales_invoice)
- self.assertEqual(si_doc.outstanding_amount, 0)
+
+ for overdue_payment in dunning.overdue_payments:
+ outstanding_amount = frappe.get_value(
+ "Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount"
+ )
+ self.assertEqual(outstanding_amount, 0)
+
+ dunning.reload()
+ self.assertEqual(dunning.status, "Resolved")
+
+ def test_dunning_and_payment_against_partially_due_invoice(self):
+ """
+ Create SI with first installment overdue. Check impact of Dunning and Payment Entry.
+ """
+ create_payment_terms_template_for_dunning()
+ sales_invoice = create_sales_invoice_against_cost_center(
+ posting_date=add_days(today(), -1 * 6),
+ qty=1,
+ rate=100,
+ do_not_submit=True,
+ )
+ sales_invoice.payment_terms_template = "_Test 50-50 for Dunning"
+ sales_invoice.submit()
+ dunning = create_dunning_from_sales_invoice(sales_invoice.name)
+
+ self.assertEqual(len(dunning.overdue_payments), 1)
+ self.assertEqual(dunning.overdue_payments[0].payment_term, "_Test Payment Term 1 for Dunning")
+
+ dunning.submit()
+ pe = get_payment_entry("Dunning", dunning.name)
+ pe.reference_no, pe.reference_date = "2", nowdate()
+ pe.insert()
+ pe.submit()
+ sales_invoice.load_from_db()
+ dunning.load_from_db()
+
+ self.assertEqual(sales_invoice.status, "Partly Paid")
+ self.assertEqual(sales_invoice.payment_schedule[0].outstanding, 0)
+ self.assertEqual(dunning.status, "Resolved")
+
+ # Test impact on cancellation of PE
+ pe.cancel()
+ sales_invoice.reload()
+ dunning.reload()
+
+ self.assertEqual(sales_invoice.status, "Overdue")
+ self.assertEqual(dunning.status, "Unresolved")
-def create_dunning():
- posting_date = add_days(today(), -20)
- due_date = add_days(today(), -15)
+def create_dunning(overdue_days, dunning_type_name=None):
+ posting_date = add_days(today(), -1 * overdue_days)
sales_invoice = create_sales_invoice_against_cost_center(
- posting_date=posting_date, due_date=due_date, status="Overdue"
+ posting_date=posting_date, qty=1, rate=100
)
- dunning_type = frappe.get_doc("Dunning Type", "First Notice")
- dunning = frappe.new_doc("Dunning")
- dunning.sales_invoice = sales_invoice.name
- dunning.customer_name = sales_invoice.customer_name
- dunning.outstanding_amount = sales_invoice.outstanding_amount
- dunning.debit_to = sales_invoice.debit_to
- dunning.currency = sales_invoice.currency
- dunning.company = sales_invoice.company
- dunning.posting_date = nowdate()
- dunning.due_date = sales_invoice.due_date
- dunning.dunning_type = "First Notice"
- dunning.rate_of_interest = dunning_type.rate_of_interest
- dunning.dunning_fee = dunning_type.dunning_fee
- dunning.save()
- return dunning
+ dunning = create_dunning_from_sales_invoice(sales_invoice.name)
+
+ if dunning_type_name:
+ dunning_type = frappe.get_doc("Dunning Type", dunning_type_name)
+ dunning.dunning_type = dunning_type.name
+ dunning.rate_of_interest = dunning_type.rate_of_interest
+ dunning.dunning_fee = dunning_type.dunning_fee
+ dunning.income_account = dunning_type.income_account
+ dunning.cost_center = dunning_type.cost_center
+
+ return dunning.save()
-def create_dunning_with_zero_interest_rate():
- posting_date = add_days(today(), -20)
- due_date = add_days(today(), -15)
- sales_invoice = create_sales_invoice_against_cost_center(
- posting_date=posting_date, due_date=due_date, status="Overdue"
- )
- dunning_type = frappe.get_doc("Dunning Type", "First Notice with 0% Rate of Interest")
- dunning = frappe.new_doc("Dunning")
- dunning.sales_invoice = sales_invoice.name
- dunning.customer_name = sales_invoice.customer_name
- dunning.outstanding_amount = sales_invoice.outstanding_amount
- dunning.debit_to = sales_invoice.debit_to
- dunning.currency = sales_invoice.currency
- dunning.company = sales_invoice.company
- dunning.posting_date = nowdate()
- dunning.due_date = sales_invoice.due_date
- dunning.dunning_type = "First Notice with 0% Rate of Interest"
- dunning.rate_of_interest = dunning_type.rate_of_interest
- dunning.dunning_fee = dunning_type.dunning_fee
- dunning.save()
- return dunning
+def create_dunning_type(title, fee, interest, is_default):
+ company = "_Test Company"
+ if frappe.db.exists("Dunning Type", f"{title} - _TC"):
+ return
-
-def create_dunning_type():
dunning_type = frappe.new_doc("Dunning Type")
- dunning_type.dunning_type = "First Notice"
- dunning_type.start_day = 10
- dunning_type.end_day = 20
- dunning_type.dunning_fee = 20
- dunning_type.rate_of_interest = 8
+ dunning_type.dunning_type = title
+ dunning_type.company = company
+ dunning_type.is_default = is_default
+ dunning_type.dunning_fee = fee
+ dunning_type.rate_of_interest = interest
+ dunning_type.income_account = get_income_account(company)
+ dunning_type.cost_center = get_default_cost_center(company)
dunning_type.append(
"dunning_letter_text",
{
"language": "en",
- "body_text": "We have still not received payment for our invoice ",
+ "body_text": "We have still not received payment for our invoice",
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees.",
},
)
- dunning_type.save()
+ dunning_type.insert()
-def create_dunning_type_with_zero_interest_rate():
- dunning_type = frappe.new_doc("Dunning Type")
- dunning_type.dunning_type = "First Notice with 0% Rate of Interest"
- dunning_type.start_day = 10
- dunning_type.end_day = 20
- dunning_type.dunning_fee = 20
- dunning_type.rate_of_interest = 0
- dunning_type.append(
- "dunning_letter_text",
- {
- "language": "en",
- "body_text": "We have still not received payment for our invoice ",
- "closing_text": "We kindly request that you pay the outstanding amount immediately, and late fees.",
- },
+def get_income_account(company):
+ return (
+ frappe.get_value("Company", company, "default_income_account")
+ or frappe.get_all(
+ "Account",
+ filters={"is_group": 0, "company": company},
+ or_filters={
+ "report_type": "Profit and Loss",
+ "account_type": ("in", ("Income Account", "Temporary")),
+ },
+ limit=1,
+ pluck="name",
+ )[0]
)
- dunning_type.save()
+
+
+def create_payment_terms_template_for_dunning():
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_term
+
+ create_payment_term("_Test Payment Term 1 for Dunning")
+ create_payment_term("_Test Payment Term 2 for Dunning")
+
+ if not frappe.db.exists("Payment Terms Template", "_Test 50-50 for Dunning"):
+ frappe.get_doc(
+ {
+ "doctype": "Payment Terms Template",
+ "template_name": "_Test 50-50 for Dunning",
+ "allocate_payment_based_on_payment_terms": 1,
+ "terms": [
+ {
+ "doctype": "Payment Terms Template Detail",
+ "payment_term": "_Test Payment Term 1 for Dunning",
+ "invoice_portion": 50.00,
+ "credit_days_based_on": "Day(s) after invoice date",
+ "credit_days": 5,
+ },
+ {
+ "doctype": "Payment Terms Template Detail",
+ "payment_term": "_Test Payment Term 2 for Dunning",
+ "invoice_portion": 50.00,
+ "credit_days_based_on": "Day(s) after invoice date",
+ "credit_days": 10,
+ },
+ ],
+ }
+ ).insert()
diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.js b/erpnext/accounts/doctype/dunning_type/dunning_type.js
index 54156b488dd..b2c08c1c7f2 100644
--- a/erpnext/accounts/doctype/dunning_type/dunning_type.js
+++ b/erpnext/accounts/doctype/dunning_type/dunning_type.js
@@ -1,8 +1,24 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Dunning Type', {
- // refresh: function(frm) {
-
- // }
+frappe.ui.form.on("Dunning Type", {
+ setup: function (frm) {
+ frm.set_query("income_account", () => {
+ return {
+ filters: {
+ root_type: "Income",
+ is_group: 0,
+ company: frm.doc.company,
+ },
+ };
+ });
+ frm.set_query("cost_center", () => {
+ return {
+ filters: {
+ is_group: 0,
+ company: frm.doc.company,
+ },
+ };
+ });
+ },
});
diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.json b/erpnext/accounts/doctype/dunning_type/dunning_type.json
index da436644724..5e39769735e 100644
--- a/erpnext/accounts/doctype/dunning_type/dunning_type.json
+++ b/erpnext/accounts/doctype/dunning_type/dunning_type.json
@@ -1,23 +1,26 @@
{
"actions": [],
"allow_rename": 1,
- "autoname": "field:dunning_type",
+ "beta": 1,
"creation": "2019-12-04 04:59:08.003664",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"dunning_type",
- "overdue_interval_section",
- "start_day",
- "column_break_4",
- "end_day",
+ "is_default",
+ "column_break_3",
+ "company",
"section_break_6",
"dunning_fee",
"column_break_8",
"rate_of_interest",
"text_block_section",
- "dunning_letter_text"
+ "dunning_letter_text",
+ "section_break_9",
+ "income_account",
+ "column_break_13",
+ "cost_center"
],
"fields": [
{
@@ -45,10 +48,6 @@
"fieldtype": "Table",
"options": "Dunning Letter Text"
},
- {
- "fieldname": "column_break_4",
- "fieldtype": "Column Break"
- },
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
@@ -57,33 +56,62 @@
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
- {
- "fieldname": "overdue_interval_section",
- "fieldtype": "Section Break",
- "label": "Overdue Interval"
- },
- {
- "fieldname": "start_day",
- "fieldtype": "Int",
- "label": "Start Day"
- },
- {
- "fieldname": "end_day",
- "fieldtype": "Int",
- "label": "End Day"
- },
{
"fieldname": "rate_of_interest",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Rate of Interest (%) Yearly"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_default",
+ "fieldtype": "Check",
+ "label": "Is Default"
+ },
+ {
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break",
+ "label": "Accounting Details"
+ },
+ {
+ "fieldname": "income_account",
+ "fieldtype": "Link",
+ "label": "Income Account",
+ "options": "Account"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_13",
+ "fieldtype": "Column Break"
}
],
- "links": [],
- "modified": "2020-07-15 17:14:17.835074",
+ "links": [
+ {
+ "link_doctype": "Dunning",
+ "link_fieldname": "dunning_type"
+ }
+ ],
+ "modified": "2021-11-13 00:25:35.659283",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Dunning Type",
+ "naming_rule": "By script",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.py b/erpnext/accounts/doctype/dunning_type/dunning_type.py
index 1b9bb9c0325..226e159a3bb 100644
--- a/erpnext/accounts/doctype/dunning_type/dunning_type.py
+++ b/erpnext/accounts/doctype/dunning_type/dunning_type.py
@@ -2,9 +2,11 @@
# For license information, please see license.txt
-# import frappe
+import frappe
from frappe.model.document import Document
class DunningType(Document):
- pass
+ def autoname(self):
+ company_abbr = frappe.get_value("Company", self.company, "abbr")
+ self.name = f"{self.dunning_type} - {company_abbr}"
diff --git a/erpnext/accounts/doctype/dunning_type/test_records.json b/erpnext/accounts/doctype/dunning_type/test_records.json
new file mode 100644
index 00000000000..7f28aab873c
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_type/test_records.json
@@ -0,0 +1,36 @@
+[
+ {
+ "doctype": "Dunning Type",
+ "dunning_type": "_Test First Notice",
+ "company": "_Test Company",
+ "is_default": 1,
+ "dunning_fee": 0.0,
+ "rate_of_interest": 0.0,
+ "dunning_letter_text": [
+ {
+ "language": "en",
+ "body_text": "We have still not received payment for our invoice",
+ "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
+ }
+ ],
+ "income_account": "Sales - _TC",
+ "cost_center": "_Test Cost Center - _TC"
+ },
+ {
+ "doctype": "Dunning Type",
+ "dunning_type": "_Test Second Notice",
+ "company": "_Test Company",
+ "is_default": 0,
+ "dunning_fee": 10.0,
+ "rate_of_interest": 10.0,
+ "dunning_letter_text": [
+ {
+ "language": "en",
+ "body_text": "We have still not received payment for our invoice",
+ "closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
+ }
+ ],
+ "income_account": "Sales - _TC",
+ "cost_center": "_Test Cost Center - _TC"
+ }
+]
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index a51e38eefea..8d8cbefa71b 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -264,11 +264,11 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
}
if(jvd.party_type && jvd.party) {
- var party_field = "";
+ let party_field = "";
if(jvd.reference_type.indexOf("Sales")===0) {
- var party_field = "customer";
+ party_field = "customer";
} else if (jvd.reference_type.indexOf("Purchase")===0) {
- var party_field = "supplier";
+ party_field = "supplier";
}
if (party_field) {
@@ -368,7 +368,7 @@ cur_frm.cscript.update_totals = function(doc) {
td += flt(accounts[i].debit, precision("debit", accounts[i]));
tc += flt(accounts[i].credit, precision("credit", accounts[i]));
}
- var doc = locals[doc.doctype][doc.name];
+ doc = locals[doc.doctype][doc.name];
doc.total_debit = td;
doc.total_credit = tc;
doc.difference = flt((td - tc), precision("difference"));
diff --git a/erpnext/accounts/doctype/overdue_payment/__init__.py b/erpnext/accounts/doctype/overdue_payment/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/accounts/doctype/overdue_payment/overdue_payment.json b/erpnext/accounts/doctype/overdue_payment/overdue_payment.json
new file mode 100644
index 00000000000..99e16469d04
--- /dev/null
+++ b/erpnext/accounts/doctype/overdue_payment/overdue_payment.json
@@ -0,0 +1,170 @@
+{
+ "actions": [],
+ "creation": "2021-09-15 18:34:27.172906",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "sales_invoice",
+ "payment_schedule",
+ "dunning_level",
+ "payment_term",
+ "section_break_15",
+ "description",
+ "section_break_4",
+ "due_date",
+ "overdue_days",
+ "mode_of_payment",
+ "column_break_5",
+ "invoice_portion",
+ "section_break_16",
+ "payment_amount",
+ "outstanding",
+ "paid_amount",
+ "discounted_amount",
+ "interest"
+ ],
+ "fields": [
+ {
+ "columns": 2,
+ "fieldname": "payment_term",
+ "fieldtype": "Link",
+ "label": "Payment Term",
+ "options": "Payment Term",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "section_break_15",
+ "fieldtype": "Section Break",
+ "label": "Description"
+ },
+ {
+ "columns": 2,
+ "fetch_from": "payment_term.description",
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "Description",
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break"
+ },
+ {
+ "columns": 2,
+ "fieldname": "due_date",
+ "fieldtype": "Date",
+ "label": "Due Date",
+ "read_only": 1
+ },
+ {
+ "fieldname": "mode_of_payment",
+ "fieldtype": "Link",
+ "label": "Mode of Payment",
+ "options": "Mode of Payment",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "columns": 2,
+ "fieldname": "invoice_portion",
+ "fieldtype": "Percent",
+ "label": "Invoice Portion",
+ "read_only": 1
+ },
+ {
+ "columns": 2,
+ "fieldname": "payment_amount",
+ "fieldtype": "Currency",
+ "label": "Payment Amount",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "payment_amount",
+ "fieldname": "outstanding",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Outstanding",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "depends_on": "paid_amount",
+ "fieldname": "paid_amount",
+ "fieldtype": "Currency",
+ "label": "Paid Amount",
+ "options": "currency"
+ },
+ {
+ "default": "0",
+ "depends_on": "discounted_amount",
+ "fieldname": "discounted_amount",
+ "fieldtype": "Currency",
+ "label": "Discounted Amount",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "sales_invoice",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Sales Invoice",
+ "options": "Sales Invoice",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "payment_schedule",
+ "fieldtype": "Data",
+ "label": "Payment Schedule",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "overdue_days",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Overdue Days",
+ "read_only": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "dunning_level",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Dunning Level",
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_16",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "interest",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Interest",
+ "options": "currency",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-09-23 13:48:27.898830",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Overdue Payment",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/overdue_payment/overdue_payment.py b/erpnext/accounts/doctype/overdue_payment/overdue_payment.py
new file mode 100644
index 00000000000..6a543ad4674
--- /dev/null
+++ b/erpnext/accounts/doctype/overdue_payment/overdue_payment.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class OverduePayment(Document):
+ pass
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 0701435dfc7..ed18feaf57d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -1,10 +1,12 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-{% include "erpnext/public/js/controllers/accounts.js" %}
frappe.provide("erpnext.accounts.dimensions");
cur_frm.cscript.tax_table = "Advance Taxes and Charges";
+erpnext.accounts.taxes.setup_tax_validations("Payment Entry");
+erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges");
+
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', "Repost Payment Ledger"];
@@ -106,12 +108,11 @@ frappe.ui.form.on('Payment Entry', {
});
frm.set_query("reference_doctype", "references", function() {
+ let doctypes = ["Journal Entry"];
if (frm.doc.party_type == "Customer") {
- var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
+ doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
} else if (frm.doc.party_type == "Supplier") {
- var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
- } else {
- var doctypes = ["Journal Entry"];
+ doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
}
return {
@@ -122,13 +123,10 @@ frappe.ui.form.on('Payment Entry', {
frm.set_query('payment_term', 'references', function(frm, cdt, cdn) {
const child = locals[cdt][cdn];
if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) {
- let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name});
-
- payment_term_list = payment_term_list.map(pt => pt.payment_term);
-
return {
+ query: "erpnext.controllers.queries.get_payment_terms_for_references",
filters: {
- 'name': ['in', payment_term_list]
+ 'reference': child.reference_name
}
}
}
@@ -165,6 +163,7 @@ frappe.ui.form.on('Payment Entry', {
},
company: function(frm) {
+ frm.trigger('party');
frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm);
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
@@ -287,6 +286,13 @@ frappe.ui.form.on('Payment Entry', {
}
},
+ mode_of_payment: function(frm) {
+ erpnext.accounts.pos.get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){
+ let payment_account_field = frm.doc.payment_type == "Receive" ? "paid_to" : "paid_from";
+ frm.set_value(payment_account_field, account);
+ })
+ },
+
party_type: function(frm) {
let party_types = Object.keys(frappe.boot.party_account_types);
@@ -319,10 +325,6 @@ frappe.ui.form.on('Payment Entry', {
}
},
- company: function(frm){
- frm.trigger('party');
- },
-
party: function(frm) {
if (frm.doc.contact_email || frm.doc.contact_person) {
frm.set_value("contact_email", "");
@@ -1106,7 +1108,7 @@ frappe.ui.form.on('Payment Entry', {
if (tax.charge_type === 'On Net Total') {
tax.charge_type = 'On Paid Amount';
}
- me.frm.add_child("taxes", tax);
+ frm.add_child("taxes", tax);
}
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
@@ -1222,7 +1224,7 @@ frappe.ui.form.on('Payment Entry', {
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
} else {
tax.grand_total_fraction_for_current_item =
- me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item +
+ frm.doc["taxes"][i-1].grand_total_fraction_for_current_item +
tax.tax_fraction_for_current_item;
}
@@ -1269,7 +1271,7 @@ frappe.ui.form.on('Payment Entry', {
}
});
- $.each(me.frm.doc["taxes"] || [], function(i, tax) {
+ $.each(frm.doc["taxes"] || [], function(i, tax) {
let current_tax_amount = frm.events.get_current_tax_amount(frm, tax);
// Adjust divisional loss to the last item
@@ -1463,4 +1465,4 @@ frappe.ui.form.on('Payment Entry', {
});
}
},
-})
\ No newline at end of file
+})
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index dcd7295bae3..596881a4e07 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -207,6 +207,20 @@ class PaymentEntry(AccountsController):
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
+ def term_based_allocation_enabled_for_reference(
+ self, reference_doctype: str, reference_name: str
+ ) -> bool:
+ if (
+ reference_doctype
+ and reference_doctype in ["Sales Invoice", "Sales Order", "Purchase Order", "Purchase Invoice"]
+ and reference_name
+ ):
+ if template := frappe.db.get_value(reference_doctype, reference_name, "payment_terms_template"):
+ return frappe.db.get_value(
+ "Payment Terms Template", template, "allocate_payment_based_on_payment_terms"
+ )
+ return False
+
def validate_allocated_amount_with_latest_data(self):
latest_references = get_outstanding_reference_documents(
{
@@ -226,10 +240,25 @@ class PaymentEntry(AccountsController):
latest_lookup = {}
for d in latest_references:
d = frappe._dict(d)
- latest_lookup.update({(d.voucher_type, d.voucher_no): d})
+ latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d
- for d in self.get("references"):
- latest = latest_lookup.get((d.reference_doctype, d.reference_name))
+ for idx, d in enumerate(self.get("references"), start=1):
+ latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()
+
+ # If term based allocation is enabled, throw
+ if (
+ d.payment_term is None or d.payment_term == ""
+ ) and self.term_based_allocation_enabled_for_reference(
+ d.reference_doctype, d.reference_name
+ ):
+ frappe.throw(
+ _(
+ "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
+ ).format(frappe.bold(d.reference_name), frappe.bold(idx))
+ )
+
+ # if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
+ latest = latest.get(d.payment_term) or latest.get(None)
# The reference has already been fully paid
if not latest:
@@ -251,6 +280,18 @@ class PaymentEntry(AccountsController):
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
+ if d.payment_term and (
+ (flt(d.allocated_amount)) > 0
+ and flt(d.allocated_amount) > flt(latest.payment_term_outstanding)
+ ):
+ frappe.throw(
+ _(
+ "Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
+ ).format(
+ d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term
+ )
+ )
+
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
@@ -1500,7 +1541,9 @@ def get_outstanding_reference_documents(args, validate=False):
accounting_dimensions=accounting_dimensions_filter,
)
- outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
+ outstanding_invoices = split_invoices_based_on_payment_terms(
+ outstanding_invoices, args.get("company")
+ )
for d in outstanding_invoices:
d["exchange_rate"] = 1
@@ -1560,8 +1603,27 @@ def get_outstanding_reference_documents(args, validate=False):
return data
-def split_invoices_based_on_payment_terms(outstanding_invoices):
+def split_invoices_based_on_payment_terms(outstanding_invoices, company):
invoice_ref_based_on_payment_terms = {}
+
+ company_currency = (
+ frappe.db.get_value("Company", company, "default_currency") if company else None
+ )
+ exc_rates = frappe._dict()
+ for doctype in ["Sales Invoice", "Purchase Invoice"]:
+ invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
+ for x in frappe.db.get_all(
+ doctype,
+ filters={"name": ["in", invoices]},
+ fields=["name", "currency", "conversion_rate", "party_account_currency"],
+ ):
+ exc_rates[x.name] = frappe._dict(
+ conversion_rate=x.conversion_rate,
+ currency=x.currency,
+ party_account_currency=x.party_account_currency,
+ company_currency=company_currency,
+ )
+
for idx, d in enumerate(outstanding_invoices):
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
payment_term_template = frappe.db.get_value(
@@ -1578,6 +1640,14 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
for payment_term in payment_schedule:
if payment_term.outstanding > 0.1:
+ doc_details = exc_rates.get(payment_term.parent, None)
+ is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
+ doc_details.party_account_currency != doc_details.company_currency
+ )
+ payment_term_outstanding = flt(payment_term.outstanding)
+ if not is_multi_currency_acc:
+ payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
+
invoice_ref_based_on_payment_terms.setdefault(idx, [])
invoice_ref_based_on_payment_terms[idx].append(
frappe._dict(
@@ -1589,6 +1659,10 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
"posting_date": d.posting_date,
"invoice_amount": flt(d.invoice_amount),
"outstanding_amount": flt(d.outstanding_amount),
+ "payment_term_outstanding": payment_term_outstanding,
+ "allocated_amount": payment_term_outstanding
+ if payment_term_outstanding
+ else d.outstanding_amount,
"payment_amount": payment_term.payment_amount,
"payment_term": payment_term.payment_term,
"account": d.account,
@@ -2010,28 +2084,27 @@ def get_payment_entry(
pe.append("references", reference)
else:
if dt == "Dunning":
+ for overdue_payment in doc.overdue_payments:
+ pe.append(
+ "references",
+ {
+ "reference_doctype": "Sales Invoice",
+ "reference_name": overdue_payment.sales_invoice,
+ "payment_term": overdue_payment.payment_term,
+ "due_date": overdue_payment.due_date,
+ "total_amount": overdue_payment.outstanding,
+ "outstanding_amount": overdue_payment.outstanding,
+ "allocated_amount": overdue_payment.outstanding,
+ },
+ )
+
pe.append(
- "references",
+ "deductions",
{
- "reference_doctype": "Sales Invoice",
- "reference_name": doc.get("sales_invoice"),
- "bill_no": doc.get("bill_no"),
- "due_date": doc.get("due_date"),
- "total_amount": doc.get("outstanding_amount"),
- "outstanding_amount": doc.get("outstanding_amount"),
- "allocated_amount": doc.get("outstanding_amount"),
- },
- )
- pe.append(
- "references",
- {
- "reference_doctype": dt,
- "reference_name": dn,
- "bill_no": doc.get("bill_no"),
- "due_date": doc.get("due_date"),
- "total_amount": doc.get("dunning_amount"),
- "outstanding_amount": doc.get("dunning_amount"),
- "allocated_amount": doc.get("dunning_amount"),
+ "account": doc.income_account,
+ "cost_center": doc.cost_center,
+ "amount": -1 * doc.dunning_amount,
+ "description": _("Interest and/or dunning fee"),
},
)
else:
@@ -2125,8 +2198,10 @@ def set_party_account_currency(dt, party_account, doc):
def set_payment_type(dt, doc):
if (
- dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)
- ) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
+ (dt == "Sales Order" or (dt == "Sales Invoice" and doc.outstanding_amount > 0))
+ or (dt == "Purchase Invoice" and doc.outstanding_amount < 0)
+ or dt == "Dunning"
+ ):
payment_type = "Receive"
else:
payment_type = "Pay"
@@ -2371,6 +2446,7 @@ def get_reference_as_per_payment_terms(
"due_date": doc.get("due_date"),
"total_amount": grand_total,
"outstanding_amount": outstanding_amount,
+ "payment_term_outstanding": payment_term_outstanding,
"payment_term": payment_term.payment_term,
"allocated_amount": payment_term_outstanding,
}
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 70cc4b3d347..c6e93f3f7a2 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -1061,6 +1061,101 @@ class TestPaymentEntry(FrappeTestCase):
}
self.assertDictEqual(ref_details, expected_response)
+ @change_settings(
+ "Accounts Settings",
+ {
+ "unlink_payment_on_cancellation_of_invoice": 1,
+ "delete_linked_ledger_entries": 1,
+ "allow_multi_currency_invoices_against_single_party_account": 1,
+ },
+ )
+ def test_overallocation_validation_on_payment_terms(self):
+ """
+ Validate Allocation on Payment Entry based on Payment Schedule. Upon overallocation, validation error must be thrown.
+
+ """
+ customer = create_customer()
+ create_payment_terms_template()
+
+ # Validate allocation on base/company currency
+ si1 = create_sales_invoice(do_not_save=1, qty=1, rate=200)
+ si1.payment_terms_template = "Test Receivable Template"
+ si1.save().submit()
+
+ si1.reload()
+ pe = get_payment_entry(si1.doctype, si1.name).save()
+ # Allocated amount should be according to the payment schedule
+ for idx, schedule in enumerate(si1.payment_schedule):
+ with self.subTest(idx=idx):
+ self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount))
+ pe.save()
+
+ # Overallocation validation should trigger
+ pe.paid_amount = 400
+ pe.references[0].allocated_amount = 200
+ pe.references[1].allocated_amount = 200
+ self.assertRaises(frappe.ValidationError, pe.save)
+ pe.delete()
+ si1.cancel()
+ si1.delete()
+
+ # Validate allocation on foreign currency
+ si2 = create_sales_invoice(
+ customer="_Test Customer USD",
+ debit_to="_Test Receivable USD - _TC",
+ currency="USD",
+ conversion_rate=80,
+ do_not_save=1,
+ )
+ si2.payment_terms_template = "Test Receivable Template"
+ si2.save().submit()
+
+ si2.reload()
+ pe = get_payment_entry(si2.doctype, si2.name).save()
+ # Allocated amount should be according to the payment schedule
+ for idx, schedule in enumerate(si2.payment_schedule):
+ with self.subTest(idx=idx):
+ self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount))
+ pe.save()
+
+ # Overallocation validation should trigger
+ pe.paid_amount = 200
+ pe.references[0].allocated_amount = 100
+ pe.references[1].allocated_amount = 100
+ self.assertRaises(frappe.ValidationError, pe.save)
+ pe.delete()
+ si2.cancel()
+ si2.delete()
+
+ # Validate allocation in base/company currency on a foreign currency document
+ # when invoice is made is foreign currency, but posted to base/company currency debtors account
+ si3 = create_sales_invoice(
+ customer=customer,
+ currency="USD",
+ conversion_rate=80,
+ do_not_save=1,
+ )
+ si3.payment_terms_template = "Test Receivable Template"
+ si3.save().submit()
+
+ si3.reload()
+ pe = get_payment_entry(si3.doctype, si3.name).save()
+ # Allocated amount should be equal to payment term outstanding
+ self.assertEqual(len(pe.references), 2)
+ for idx, ref in enumerate(pe.references):
+ with self.subTest(idx=idx):
+ self.assertEqual(ref.payment_term_outstanding, ref.allocated_amount)
+ pe.save()
+
+ # Overallocation validation should trigger
+ pe.paid_amount = 16000
+ pe.references[0].allocated_amount = 8000
+ pe.references[1].allocated_amount = 8000
+ self.assertRaises(frappe.ValidationError, pe.save)
+ pe.delete()
+ si3.cancel()
+ si3.delete()
+
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")
@@ -1150,3 +1245,17 @@ def create_payment_terms_template_with_discount(
def create_payment_term(name):
if not frappe.db.exists("Payment Term", name):
frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert()
+
+
+def create_customer(name="_Test Customer 2 USD", currency="USD"):
+ customer = None
+ if frappe.db.exists("Customer", name):
+ customer = name
+ else:
+ customer = frappe.new_doc("Customer")
+ customer.customer_name = name
+ customer.default_currency = currency
+ customer.type = "Individual"
+ customer.save()
+ customer = customer.name
+ return customer
diff --git a/erpnext/accounts/doctype/payment_order/payment_order.js b/erpnext/accounts/doctype/payment_order/payment_order.js
index 7d85d89c452..6630e7122c5 100644
--- a/erpnext/accounts/doctype/payment_order/payment_order.js
+++ b/erpnext/accounts/doctype/payment_order/payment_order.js
@@ -124,7 +124,7 @@ frappe.ui.form.on('Payment Order', {
return frappe.call({
method: "erpnext.accounts.doctype.payment_order.payment_order.make_payment_records",
args: {
- "name": me.frm.doc.name,
+ "name": frm.doc.name,
"supplier": args.supplier,
"mode_of_payment": args.mode_of_payment
},
diff --git a/erpnext/accounts/doctype/payment_term/payment_term.js b/erpnext/accounts/doctype/payment_term/payment_term.js
index feecf93484c..0898a09a1c6 100644
--- a/erpnext/accounts/doctype/payment_term/payment_term.js
+++ b/erpnext/accounts/doctype/payment_term/payment_term.js
@@ -14,7 +14,7 @@ frappe.ui.form.on('Payment Term', {
if (frm.doc.discount) {
let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]);
if (frm.doc.discount_type == 'Amount') {
- description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)]);
+ description = __("{0} will be given as discount.", [frm.doc.discount]);
}
frm.set_df_property("discount", "description", description);
}
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index 641f4528c53..49472484ef4 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -126,21 +126,22 @@ class PeriodClosingVoucher(AccountsController):
def make_gl_entries(self, get_opening_entries=False):
gl_entries = self.get_gl_entries()
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
- if gl_entries:
- if len(gl_entries) > 5000:
- frappe.enqueue(
- process_gl_entries,
- gl_entries=gl_entries,
- closing_entries=closing_entries,
- voucher_name=self.name,
- queue="long",
- )
- frappe.msgprint(
- _("The GL Entries will be processed in the background, it can take a few minutes."),
- alert=True,
- )
- else:
- process_gl_entries(gl_entries, closing_entries, voucher_name=self.name)
+ if len(gl_entries) > 5000:
+ frappe.enqueue(
+ process_gl_entries,
+ gl_entries=gl_entries,
+ closing_entries=closing_entries,
+ voucher_name=self.name,
+ company=self.company,
+ closing_date=self.posting_date,
+ queue="long",
+ )
+ frappe.msgprint(
+ _("The GL Entries will be processed in the background, it can take a few minutes."),
+ alert=True,
+ )
+ else:
+ process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
def get_grouped_gl_entries(self, get_opening_entries=False):
closing_entries = []
@@ -321,24 +322,22 @@ class PeriodClosingVoucher(AccountsController):
return query.run(as_dict=1)
-def process_gl_entries(gl_entries, closing_entries, voucher_name=None):
+def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
make_closing_entries,
)
from erpnext.accounts.general_ledger import make_gl_entries
try:
- make_gl_entries(gl_entries, merge_entries=False)
- make_closing_entries(gl_entries + closing_entries, voucher_name=voucher_name)
- frappe.db.set_value(
- "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
- )
+ if gl_entries:
+ make_gl_entries(gl_entries, merge_entries=False)
+
+ make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
+ frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
except Exception as e:
frappe.db.rollback()
frappe.log_error(e)
- frappe.db.set_value(
- "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed"
- )
+ frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
def make_reverse_gl_entries(voucher_type, voucher_no):
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index 32e267f33c8..6f0b8019b86 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -1,9 +1,10 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-{% include 'erpnext/selling/sales_common.js' %};
frappe.provide("erpnext.accounts");
+erpnext.sales_common.setup_selling_controller();
+erpnext.accounts.pos.setup("POS Invoice");
erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnext.selling.SellingController {
settings = {};
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js
index 813d20dbf93..0a89aee8e9c 100755
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.js
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js
@@ -1,8 +1,6 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-{% include "erpnext/public/js/controllers/accounts.js" %}
-
frappe.ui.form.on('POS Profile', {
setup: function(frm) {
frm.set_query("selling_price_list", function() {
@@ -148,4 +146,4 @@ frappe.ui.form.on('POS Profile', {
frm.toggle_display('expense_account',
erpnext.is_perpetual_inventory_enabled(frm.doc.company));
}
-});
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
index 08f4cf45d62..6193c849b5b 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
@@ -140,7 +140,7 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency):
def get_ar_filters(doc, entry):
return {
"report_date": doc.posting_date if doc.posting_date else None,
- "customer_name": entry.customer,
+ "customer": entry.customer,
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
"sales_partner": doc.sales_partner if doc.sales_partner else None,
"sales_person": doc.sales_person if doc.sales_person else None,
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html
index 07e1896292d..259526f8c43 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html
@@ -10,16 +10,12 @@
{{ _(report.report_name) }}
- {% if (filters.customer_name) %}
- {{ filters.customer_name }}
- {% else %}
- {{ filters.customer ~ filters.supplier }}
- {% endif %}
+ {{ filters.customer }}
- {% if (filters.tax_id) %}
- {{ _("Tax Id: ") }}{{ filters.tax_id }}
- {% endif %}
+ {% if (filters.tax_id) %}
+ {{ _("Tax Id: ") }}{{ filters.tax_id }}
+ {% endif %}
{{ _(filters.ageing_based_on) }}
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 6a558ca606b..c19413d8117 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -2,7 +2,11 @@
// License: GNU General Public License v3. See license.txt
frappe.provide("erpnext.accounts");
-{% include 'erpnext/public/js/controllers/buying.js' %};
+
+erpnext.accounts.payment_triggers.setup("Purchase Invoice");
+erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
+erpnext.accounts.taxes.setup_tax_validations("Purchase Invoice");
+erpnext.buying.setup_buying_controller();
erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.BuyingController {
setup(doc) {
@@ -506,7 +510,8 @@ frappe.ui.form.on("Purchase Invoice", {
setup: function(frm) {
frm.custom_make_buttons = {
'Purchase Invoice': 'Return / Debit Note',
- 'Payment Entry': 'Payment'
+ 'Payment Entry': 'Payment',
+ 'Landed Cost Voucher': function () { frm.trigger('create_landed_cost_voucher') },
}
frm.set_query("additional_discount_account", function() {
@@ -544,6 +549,26 @@ frappe.ui.form.on("Purchase Invoice", {
frm.events.add_custom_buttons(frm);
},
+ mode_of_payment: function(frm) {
+ erpnext.accounts.pos.get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account) {
+ frm.set_value("cash_bank_account", account);
+ })
+ },
+
+ create_landed_cost_voucher: function (frm) {
+ let lcv = frappe.model.get_new_doc('Landed Cost Voucher');
+ lcv.company = frm.doc.company;
+
+ let lcv_receipt = frappe.model.get_new_doc('Landed Cost Purchase Invoice');
+ lcv_receipt.receipt_document_type = 'Purchase Invoice';
+ lcv_receipt.receipt_document = frm.doc.name;
+ lcv_receipt.supplier = frm.doc.supplier;
+ lcv_receipt.grand_total = frm.doc.grand_total;
+ lcv.purchase_receipts = [lcv_receipt];
+
+ frappe.set_route("Form", lcv.doctype, lcv.name);
+ },
+
add_custom_buttons: function(frm) {
if (frm.doc.docstatus == 1 && frm.doc.per_received < 100) {
frm.add_custom_button(__('Purchase Receipt'), () => {
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
index eb0ea7fef81..78dc4bee1a6 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
@@ -1,30 +1,31 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
+erpnext.accounts.taxes.setup_tax_validations("Purchase Taxes and Charges Template");
+erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
-{% include "erpnext/public/js/controllers/accounts.js" %}
+frappe.ui.form.on("Purchase Taxes and Charges", {
+ add_deduct_tax(doc, cdt, cdn) {
+ let d = locals[cdt][cdn];
-frappe.ui.form.on("Purchase Taxes and Charges", "add_deduct_tax", function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
+ if(!d.category && d.add_deduct_tax) {
+ frappe.msgprint(__("Please select Category first"));
+ d.add_deduct_tax = '';
+ }
+ else if(d.category != 'Total' && d.add_deduct_tax == 'Deduct') {
+ frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Valuation and Total'"));
+ d.add_deduct_tax = '';
+ }
+ refresh_field('add_deduct_tax', d.name, 'taxes');
+ },
- if(!d.category && d.add_deduct_tax) {
- frappe.msgprint(__("Please select Category first"));
- d.add_deduct_tax = '';
+ category(doc, cdt, cdn) {
+ let d = locals[cdt][cdn];
+
+ if(d.category != 'Total' && d.add_deduct_tax == 'Deduct') {
+ frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Valuation and Total'"));
+ d.add_deduct_tax = '';
+ }
+ refresh_field('add_deduct_tax', d.name, 'taxes');
}
- else if(d.category != 'Total' && d.add_deduct_tax == 'Deduct') {
- frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Valuation and Total'"));
- d.add_deduct_tax = '';
- }
- refresh_field('add_deduct_tax', d.name, 'taxes');
-});
-
-frappe.ui.form.on("Purchase Taxes and Charges", "category", function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
-
- if (d.category != 'Total' && d.add_deduct_tax == 'Deduct') {
- frappe.msgprint(__("Cannot deduct when category is for 'Valuation' or 'Vaulation and Total'"));
- d.add_deduct_tax = '';
- }
- refresh_field('add_deduct_tax', d.name, 'taxes');
});
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/italy.js b/erpnext/accounts/doctype/sales_invoice/regional/italy.js
index 21eb8ce6619..2f305b914eb 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/italy.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/italy.js
@@ -1,3 +1,23 @@
-{% include "erpnext/regional/italy/sales_invoice.js" %}
-
-erpnext.setup_e_invoice_button('Sales Invoice')
+frappe.ui.form.on("Sales Invoice", {
+ refresh: (frm) => {
+ if(frm.doc.docstatus == 1) {
+ frm.add_custom_button(__('Generate E-Invoice'), () => {
+ frm.call({
+ method: "erpnext.regional.italy.utils.generate_single_invoice",
+ args: {
+ docname: frm.doc.name
+ },
+ callback: function(r) {
+ frm.reload_doc();
+ if(r.message) {
+ open_url_post(frappe.request.url, {
+ cmd: 'frappe.core.doctype.file.file.download_file',
+ file_url: r.message
+ });
+ }
+ }
+ });
+ });
+ }
+ }
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 8753ebc3baf..8fa0a84f6ee 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -1,10 +1,13 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-{% include 'erpnext/selling/sales_common.js' %};
frappe.provide("erpnext.accounts");
-
+erpnext.accounts.taxes.setup_tax_validations("Sales Invoice");
+erpnext.accounts.payment_triggers.setup("Sales Invoice");
+erpnext.accounts.pos.setup("Sales Invoice");
+erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges");
+erpnext.sales_common.setup_selling_controller();
erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends erpnext.selling.SellingController {
setup(doc) {
this.setup_posting_date_time_check();
@@ -142,9 +145,15 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
cur_frm.events.create_invoice_discounting(cur_frm);
}, __('Create'));
- if (doc.due_date < frappe.datetime.get_today()) {
- cur_frm.add_custom_button(__('Dunning'), function() {
- cur_frm.events.create_dunning(cur_frm);
+ const payment_is_overdue = doc.payment_schedule.map(
+ row => Date.parse(row.due_date) < Date.now()
+ ).reduce(
+ (prev, current) => prev || current
+ );
+
+ if (payment_is_overdue) {
+ this.frm.add_custom_button(__('Dunning'), () => {
+ this.frm.events.create_dunning(this.frm);
}, __('Create'));
}
}
@@ -711,7 +720,7 @@ frappe.ui.form.on('Sales Invoice', {
frm.set_query('pos_profile', function(doc) {
if(!doc.company) {
- frappe.throw(_('Please set Company'));
+ frappe.throw(__('Please set Company'));
}
return {
@@ -858,7 +867,7 @@ frappe.ui.form.on('Sales Invoice', {
kwargs = Object();
}
- if (!kwargs.hasOwnProperty("project") && frm.doc.project) {
+ if (!Object.prototype.hasOwnProperty.call(kwargs, "project") && frm.doc.project) {
kwargs.project = frm.doc.project;
}
@@ -891,6 +900,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.events.append_time_log(frm, timesheet, 1.0);
}
});
+ frm.refresh_field("timesheets");
+ frm.trigger("calculate_timesheet_totals");
},
async get_exchange_rate(frm, from_currency, to_currency) {
@@ -930,9 +941,6 @@ frappe.ui.form.on('Sales Invoice', {
row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate);
row.timesheet_detail = time_log.name;
row.project_name = time_log.project_name;
-
- frm.refresh_field("timesheets");
- frm.trigger("calculate_timesheet_totals");
},
calculate_timesheet_totals: function(frm) {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 7ab1c893971..b3212b5a7b3 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -2516,55 +2516,49 @@ def get_mode_of_payment_info(mode_of_payment, company):
@frappe.whitelist()
-def create_dunning(source_name, target_doc=None):
+def create_dunning(source_name, target_doc=None, ignore_permissions=False):
from frappe.model.mapper import get_mapped_doc
- from erpnext.accounts.doctype.dunning.dunning import (
- calculate_interest_and_amount,
- get_dunning_letter_text,
- )
+ def postprocess_dunning(source, target):
+ from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text
- def set_missing_values(source, target):
- target.sales_invoice = source_name
- target.outstanding_amount = source.outstanding_amount
- overdue_days = (getdate(target.posting_date) - getdate(source.due_date)).days
- target.overdue_days = overdue_days
- if frappe.db.exists(
- "Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]}
- ):
- dunning_type = frappe.get_doc(
- "Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]}
- )
+ dunning_type = frappe.db.exists("Dunning Type", {"is_default": 1, "company": source.company})
+ if dunning_type:
+ dunning_type = frappe.get_doc("Dunning Type", dunning_type)
target.dunning_type = dunning_type.name
target.rate_of_interest = dunning_type.rate_of_interest
target.dunning_fee = dunning_type.dunning_fee
- letter_text = get_dunning_letter_text(dunning_type=dunning_type.name, doc=target.as_dict())
+ target.income_account = dunning_type.income_account
+ target.cost_center = dunning_type.cost_center
+ letter_text = get_dunning_letter_text(
+ dunning_type=dunning_type.name, doc=target.as_dict(), language=source.language
+ )
+
if letter_text:
target.body_text = letter_text.get("body_text")
target.closing_text = letter_text.get("closing_text")
target.language = letter_text.get("language")
- amounts = calculate_interest_and_amount(
- target.outstanding_amount,
- target.rate_of_interest,
- target.dunning_fee,
- target.overdue_days,
- )
- target.interest_amount = amounts.get("interest_amount")
- target.dunning_amount = amounts.get("dunning_amount")
- target.grand_total = amounts.get("grand_total")
- doclist = get_mapped_doc(
- "Sales Invoice",
- source_name,
- {
+ target.validate()
+
+ return get_mapped_doc(
+ from_doctype="Sales Invoice",
+ from_docname=source_name,
+ target_doc=target_doc,
+ table_maps={
"Sales Invoice": {
"doctype": "Dunning",
- }
+ "field_map": {"customer_address": "customer_address", "parent": "sales_invoice"},
+ },
+ "Payment Schedule": {
+ "doctype": "Overdue Payment",
+ "field_map": {"name": "payment_schedule", "parent": "sales_invoice"},
+ "condition": lambda doc: doc.outstanding > 0 and getdate(doc.due_date) < getdate(),
+ },
},
- target_doc,
- set_missing_values,
+ postprocess=postprocess_dunning,
+ ignore_permissions=ignore_permissions,
)
- return doclist
def check_if_return_invoice_linked_with_payment_entry(self):
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 0280c3590c4..41e55546a83 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1900,16 +1900,22 @@ class TestSalesInvoice(unittest.TestCase):
si = self.create_si_to_test_tax_breakup()
- itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
+ itemised_tax_data = get_itemised_tax_breakup_data(si)
- expected_itemised_tax = {
- "_Test Item": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0}},
- "_Test Item 2": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0}},
- }
- expected_itemised_taxable_amount = {"_Test Item": 10000.0, "_Test Item 2": 5000.0}
+ expected_itemised_tax = [
+ {
+ "item": "_Test Item",
+ "taxable_amount": 10000.0,
+ "Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0},
+ },
+ {
+ "item": "_Test Item 2",
+ "taxable_amount": 5000.0,
+ "Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0},
+ },
+ ]
- self.assertEqual(itemised_tax, expected_itemised_tax)
- self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
+ self.assertEqual(itemised_tax_data, expected_itemised_tax)
frappe.flags.country = None
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
index 066c4eae436..00e8b621c0d 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
@@ -1,6 +1,5 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-cur_frm.cscript.tax_table = "Sales Taxes and Charges";
-
-{% include "erpnext/public/js/controllers/accounts.js" %}
+erpnext.accounts.taxes.setup_tax_validations("Sales Taxes and Charges Template");
+erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges");
\ No newline at end of file
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index f1dad875fa7..e9dc5fc0ccc 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -13,14 +13,11 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
+from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.utils import create_payment_ledger_entry
-class ClosedAccountingPeriod(frappe.ValidationError):
- pass
-
-
def make_gl_entries(
gl_map,
cancel=False,
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 03cf82a2b04..49962036351 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -33,6 +33,7 @@ import erpnext
from erpnext import get_company_currency
from erpnext.accounts.utils import get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
+from erpnext.utilities.regional import temporary_flag
PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
SALES_TRANSACTION_TYPES = {
@@ -261,9 +262,8 @@ def set_address_details(
)
if doctype in TRANSACTION_TYPES:
- # required to set correct region
- frappe.flags.company = company
- get_regional_address_details(party_details, doctype, company)
+ with temporary_flag("company", company):
+ get_regional_address_details(party_details, doctype, company)
return party_address, shipping_address
diff --git a/erpnext/accounts/print_format/dunning_letter/dunning_letter.json b/erpnext/accounts/print_format/dunning_letter/dunning_letter.json
index a7eac70b65f..c48e1cf35b4 100644
--- a/erpnext/accounts/print_format/dunning_letter/dunning_letter.json
+++ b/erpnext/accounts/print_format/dunning_letter/dunning_letter.json
@@ -1,4 +1,5 @@
{
+ "absolute_value": 0,
"align_labels_right": 0,
"creation": "2019-12-11 04:37:14.012805",
"css": ".print-format th {\n background-color: transparent !important;\n border-bottom: 1px solid !important;\n border-top: none !important;\n}\n.print-format .ql-editor {\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.print-format table {\n margin-bottom: 0px;\n }\n.print-format .table-data tr:last-child { \n border-bottom: 1px solid !important;\n}\n\n.print-format .table-inner tr:last-child {\n border-bottom:none !important;\n}\n.print-format .table-inner {\n margin: 0px 0px;\n}\n\n.print-format .table-data ul li { \n color:#787878 !important;\n}\n\n.no-top-border {\n border-top:none !important;\n}\n\n.table-inner td {\n padding-left: 0px !important; \n padding-top: 1px !important;\n padding-bottom: 1px !important;\n color:#787878 !important;\n}\n\n.total {\n background-color: lightgrey !important;\n padding-top: 4px !important;\n padding-bottom: 4px !important;\n}\n",
@@ -9,10 +10,10 @@
"docstatus": 0,
"doctype": "Print Format",
"font": "Arial",
- "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{{doc.customer_name}}
\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n
{{_(doc.dunning_type)}}
\\n
{{ doc.name }}
\\n
\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldname\": \"sales_invoice\", \"print_hide\": 0, \"label\": \"Sales Invoice\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"label\": \"Due Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n \\n \\n | {{_(\\\"Description\\\")}} | \\n\\t {{_(\\\"Amount\\\")}} | \\n
\\n \\n | \\n {{_(\\\"Outstanding Amount\\\")}}\\n | \\n \\n {{doc.get_formatted(\\\"outstanding_amount\\\")}}\\n | \\n
\\n {%if doc.rate_of_interest > 0%}\\n \\n | \\n {{_(\\\"Interest \\\")}} {{doc.rate_of_interest}}% p.a. ({{doc.overdue_days}} {{_(\\\"days\\\")}})\\n | \\n \\n {{doc.get_formatted(\\\"interest_amount\\\")}}\\n | \\n
\\n {% endif %}\\n {%if doc.dunning_fee > 0%}\\n \\n | \\n {{_(\\\"Dunning Fee\\\")}}\\n | \\n \\n {{doc.get_formatted(\\\"dunning_fee\\\")}}\\n | \\n
\\n {% endif %}\\n \\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n\\n\\t\\t
\\n\\t\\t\\t{{_(\\\"Grand Total\\\")}}
\\n\\t\\t
\\n\\t\\t\\t{{doc.get_formatted(\\\"grand_total\\\")}}\\n\\t\\t
\\n
\\n\\n\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]",
+ "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{{doc.customer_name}}
\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n
{{_(doc.dunning_type)}}
\\n
{{ doc.name }}
\\n
\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"overdue_payments\", \"print_hide\": 0, \"label\": \"Overdue Payments\", \"visible_columns\": [{\"fieldname\": \"sales_invoice\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"dunning_level\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"due_date\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"overdue_days\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"invoice_portion\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"outstanding\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"interest\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total_outstanding\", \"print_hide\": 0, \"label\": \"Total Outstanding\"}, {\"fieldname\": \"dunning_fee\", \"print_hide\": 0, \"label\": \"Dunning Fee\"}, {\"fieldname\": \"total_interest\", \"print_hide\": 0, \"label\": \"Total Interest\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]",
"idx": 0,
"line_breaks": 0,
- "modified": "2020-07-14 18:25:44.348207",
+ "modified": "2021-09-30 10:22:02.603871",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Dunning Letter",
diff --git a/erpnext/accounts/report/account_balance/account_balance.js b/erpnext/accounts/report/account_balance/account_balance.js
index bb66951cdcd..5681be92115 100644
--- a/erpnext/accounts/report/account_balance/account_balance.js
+++ b/erpnext/accounts/report/account_balance/account_balance.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Account Balance"] = {
"filters": [
diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
index e1fccb6e720..7617ed1e227 100644
--- a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
+++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports['Billed Items To Be Received'] = {
'filters': [
@@ -17,7 +17,7 @@ frappe.query_reports['Billed Items To Be Received'] = {
'fieldname': 'posting_date',
'fieldtype': 'Date',
'reqd': 1,
- 'default': get_today()
+ 'default': frappe.datetime.get_today()
},
{
'label': __('Purchase Invoice'),
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 d58fd95a840..1afa8d56252 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Consolidated Financial Statement"] = {
diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js
index a1236316638..74d52de2d2e 100644
--- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js
+++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Customer Ledger Summary"] = {
"filters": [
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js
index 96e0c844ca5..eec904ec854 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
function get_filters() {
let filters = [
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
index 9d416db4fdd..fc712fb5a75 100644
--- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js
index 92cf36ebc52..f6b0b8c3f7f 100644
--- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Gross and Net Profit Report"] = {
"filters": [
diff --git a/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.js b/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.js
index 7908c07a0ad..bd9b54398b8 100644
--- a/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.js
+++ b/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Inactive Sales Items"] = {
"filters": [
diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.js b/erpnext/accounts/report/payment_ledger/payment_ledger.js
index a5a4108f1df..65380ccdda6 100644
--- a/erpnext/accounts/report/payment_ledger/payment_ledger.js
+++ b/erpnext/accounts/report/payment_ledger/payment_ledger.js
@@ -1,6 +1,6 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
function get_filters() {
let filters = [
diff --git a/erpnext/accounts/report/pos_register/pos_register.js b/erpnext/accounts/report/pos_register/pos_register.js
index b8d48d92de0..6e5491a0f8f 100644
--- a/erpnext/accounts/report/pos_register/pos_register.js
+++ b/erpnext/accounts/report/pos_register/pos_register.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["POS Register"] = {
"filters": [
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js
index 6caebd34a2f..27b29baa40a 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js
@@ -16,9 +16,30 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"fieldname": "based_on",
"label": __("Based On"),
"fieldtype": "Select",
- "options": ["Cost Center", "Project"],
+ "options": ["Cost Center", "Project", "Accounting Dimension"],
"default": "Cost Center",
- "reqd": 1
+ "reqd": 1,
+ "on_change": function(query_report){
+ let based_on = query_report.get_values().based_on;
+ if(based_on!='Accounting Dimension'){
+ frappe.query_report.set_filter_value({
+ accounting_dimension: ''
+ });
+ }
+ }
+ },
+ {
+ "fieldname": "accounting_dimension",
+ "label": __("Accounting Dimension"),
+ "fieldtype": "Link",
+ "options": "Accounting Dimension",
+ "get_query": () =>{
+ return {
+ filters: {
+ "disabled": 0
+ }
+ }
+ }
},
{
"fieldname": "fiscal_year",
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
index 183e279fe5d..3d6e9b5428c 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
@@ -6,6 +6,7 @@ import frappe
from frappe import _
from frappe.utils import cstr, flt
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.report.financial_statements import (
filter_accounts,
filter_out_zero_value_rows,
@@ -16,10 +17,12 @@ value_fields = ("income", "expense", "gross_profit_loss")
def execute(filters=None):
- if not filters.get("based_on"):
- filters["based_on"] = "Cost Center"
+ if filters.get("based_on") == "Accounting Dimension" and not filters.get("accounting_dimension"):
+ frappe.throw(_("Select Accounting Dimension."))
- based_on = filters.based_on.replace(" ", "_").lower()
+ based_on = (
+ filters.based_on if filters.based_on != "Accounting Dimension" else filters.accounting_dimension
+ )
validate_filters(filters)
accounts = get_accounts_data(based_on, filters.get("company"))
data = get_data(accounts, filters, based_on)
@@ -28,14 +31,14 @@ def execute(filters=None):
def get_accounts_data(based_on, company):
- if based_on == "cost_center":
+ if based_on == "Cost Center":
return frappe.db.sql(
"""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt
from `tabCost Center` where company=%s order by name""",
company,
as_dict=True,
)
- elif based_on == "project":
+ elif based_on == "Project":
return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name")
else:
filters = {}
@@ -56,11 +59,17 @@ def get_data(accounts, filters, based_on):
gl_entries_by_account = {}
+ accounting_dimensions = get_dimensions(with_cost_center_and_project=True)[0]
+ fieldname = ""
+ for dimension in accounting_dimensions:
+ if dimension["document_type"] == based_on:
+ fieldname = dimension["fieldname"]
+
set_gl_entries_by_account(
filters.get("company"),
filters.get("from_date"),
filters.get("to_date"),
- based_on,
+ fieldname,
gl_entries_by_account,
ignore_closing_entries=not flt(filters.get("with_period_closing_entry")),
)
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 44e20e83c50..92e6f7f5141 100644
--- a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js
+++ b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.js
@@ -29,7 +29,6 @@ frappe.query_reports["Sales Payment Summary"] = {
"label": __("Owner"),
"fieldtype": "Link",
"options": "User",
- "defaults": user
},
{
"fieldname":"is_pos",
diff --git a/erpnext/accounts/report/share_balance/share_balance.js b/erpnext/accounts/report/share_balance/share_balance.js
index 6db5bdd299e..ac64a0bfb9f 100644
--- a/erpnext/accounts/report/share_balance/share_balance.js
+++ b/erpnext/accounts/report/share_balance/share_balance.js
@@ -1,7 +1,7 @@
// -*- coding: utf-8 -*-
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Share Balance"] = {
"filters": [
diff --git a/erpnext/accounts/report/share_ledger/share_ledger.js b/erpnext/accounts/report/share_ledger/share_ledger.js
index 6d1c44a6d01..4f2d2cc78f8 100644
--- a/erpnext/accounts/report/share_ledger/share_ledger.js
+++ b/erpnext/accounts/report/share_ledger/share_ledger.js
@@ -1,7 +1,7 @@
// -*- coding: utf-8 -*-
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Share Ledger"] = {
"filters": [
diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js
index 5dc4c3d1c15..8e3c8ac6300 100644
--- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js
+++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Supplier Ledger Summary"] = {
"filters": [
diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js
index c29a801059b..e330d2f7fe8 100644
--- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js
+++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Tax Withholding Details"] = {
"filters": [
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js
index d3d45b353a6..d3348460a88 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["TDS Computation Summary"] = {
"filters": [
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 39917f90c93..599c8a312a5 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -231,6 +231,9 @@ def get_opening_balance(
(closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes")
)
+ if doctype == "GL Entry":
+ opening_balance = opening_balance.where(closing_balance.is_cancelled == 0)
+
if (
not filters.show_unclosed_fy_pl_balances
and report_type == "Profit and Loss"
diff --git a/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js
index 0c148f85fb8..f7ab029f194 100644
--- a/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js
+++ b/erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js
@@ -1,6 +1,6 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Voucher-wise Balance"] = {
"filters": [
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 4b54483bc0c..e3546631514 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -1112,7 +1112,8 @@ def get_autoname_with_number(number_value, doc_title, company):
def parse_naming_series_variable(doc, variable):
if variable == "FY":
- return get_fiscal_year(date=doc.get("posting_date"), company=doc.get("company"))[0]
+ date = doc.get("posting_date") or doc.get("transaction_date") or getdate()
+ return get_fiscal_year(date=date, company=doc.get("company"))[0]
@frappe.whitelist()
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 43920adca36..97a41de526b 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -473,7 +473,7 @@ frappe.ui.form.on('Asset', {
}
const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code);
if (!item) {
- doctype_field = frappe.scrub(doctype)
+ let doctype_field = frappe.scrub(doctype)
frm.set_value(doctype_field, '');
frappe.msgprint({
title: __('Invalid {0}', [__(doctype)]),
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 42f531189aa..7dc438e08f6 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -933,6 +933,8 @@ def create_new_asset_after_split(asset, split_qty):
)
new_asset.gross_purchase_amount = new_gross_purchase_amount
+ if asset.purchase_receipt_amount:
+ new_asset.purchase_receipt_amount = new_gross_purchase_amount
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
new_asset.asset_quantity = split_qty
new_asset.split_from = asset.name
diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py
index 22055dcb736..b85f7194f98 100644
--- a/erpnext/assets/doctype/asset_movement/asset_movement.py
+++ b/erpnext/assets/doctype/asset_movement/asset_movement.py
@@ -62,20 +62,21 @@ class AssetMovement(Document):
frappe.throw(_("Source and Target Location cannot be same"))
if self.purpose == "Receipt":
- if not (d.source_location or d.from_employee) and not (d.target_location or d.to_employee):
+ if not (d.source_location) and not (d.target_location or d.to_employee):
frappe.throw(
_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
)
- elif d.from_employee and not d.target_location:
- frappe.throw(
- _("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
- )
- elif d.to_employee and d.target_location:
- frappe.throw(
- _(
- "Asset {0} cannot be received at a location and given to an employee in a single movement"
- ).format(d.asset)
- )
+ elif d.source_location:
+ if d.from_employee and not d.target_location:
+ frappe.throw(
+ _("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
+ )
+ elif d.to_employee and d.target_location:
+ frappe.throw(
+ _(
+ "Asset {0} cannot be received at a location and given to an employee in a single movement"
+ ).format(d.asset)
+ )
def validate_employee(self):
for d in self.assets:
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 48b17f58fb2..48d33314ec2 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Fixed Asset Register"] = {
"filters": [
diff --git a/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json b/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json
index 6452ed2139b..751796bbbb5 100644
--- a/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json
+++ b/erpnext/buying/dashboard_chart/purchase_order_trends/purchase_order_trends.json
@@ -5,18 +5,19 @@
"custom_options": "{\"type\": \"line\", \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}, \"lineOptions\": {\"regionFill\": 1}}",
"docstatus": 0,
"doctype": "Dashboard Chart",
- "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
"filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Item\"}",
- "idx": 0,
+ "idx": 1,
"is_public": 1,
"is_standard": 1,
- "modified": "2020-07-21 16:13:25.092287",
+ "modified": "2023-07-19 13:06:42.937941",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Trends",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Purchase Order Trends",
+ "roles": [],
"timeseries": 0,
"type": "Line",
"use_report_chart": 1,
diff --git a/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json b/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json
index 6f7da8ea870..f6b97175398 100644
--- a/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json
+++ b/erpnext/buying/dashboard_chart/top_suppliers/top_suppliers.json
@@ -4,18 +4,19 @@
"creation": "2020-07-20 21:01:02.329519",
"docstatus": 0,
"doctype": "Dashboard Chart",
- "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
+ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
"filters_json": "{\"period\":\"Monthly\",\"period_based_on\":\"posting_date\",\"based_on\":\"Supplier\"}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
- "modified": "2020-07-22 12:43:40.829652",
+ "modified": "2023-07-19 13:07:41.753556",
"modified_by": "Administrator",
"module": "Buying",
"name": "Top Suppliers",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Purchase Receipt Trends",
+ "roles": [],
"timeseries": 0,
"type": "Bar",
"use_report_chart": 1,
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 8fa8f305549..db3a60e9421 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -3,7 +3,10 @@
frappe.provide("erpnext.buying");
frappe.provide("erpnext.accounts.dimensions");
-{% include 'erpnext/public/js/controllers/buying.js' %};
+
+erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
+erpnext.accounts.taxes.setup_tax_validations("Purchase Order");
+erpnext.buying.setup_buying_controller();
frappe.ui.form.on("Purchase Order", {
setup: function(frm) {
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index 2f0b7862a82..0cdb915cdce 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -1,11 +1,10 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-
-{% include 'erpnext/public/js/controllers/buying.js' %};
-
cur_frm.add_fetch('contact', 'email_id', 'email_id')
+erpnext.buying.setup_buying_controller();
+
frappe.ui.form.on("Request for Quotation",{
setup: function(frm) {
frm.custom_make_buttons = {
@@ -436,7 +435,7 @@ erpnext.buying.RequestforQuotationController = class RequestforQuotationControll
//Remove blanks
for (var j = 0; j < frm.doc.suppliers.length; j++) {
- if(!frm.doc.suppliers[j].hasOwnProperty("supplier")) {
+ if(!Object.prototype.hasOwnProperty.call(frm.doc.suppliers[j], "supplier")) {
frm.get_field("suppliers").grid.grid_rows[j].remove();
}
}
@@ -445,10 +444,11 @@ erpnext.buying.RequestforQuotationController = class RequestforQuotationControll
if(r.message) {
for (var i = 0; i < r.message.length; i++) {
var exists = false;
+ let supplier = "";
if (r.message[i].constructor === Array){
- var supplier = r.message[i][0];
+ supplier = r.message[i][0];
} else {
- var supplier = r.message[i].name;
+ supplier = r.message[i].name;
}
for (var j = 0; j < doc.suppliers.length;j++) {
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
index dc9c590dc5e..addf5a5e77b 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
@@ -1,9 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-// attach required files
-{% include 'erpnext/public/js/controllers/buying.js' %};
-
+erpnext.buying.setup_buying_controller();
erpnext.buying.SupplierQuotationController = class SupplierQuotationController extends erpnext.buying.BuyingController {
setup() {
this.frm.custom_make_buttons = {
diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js
index b4cd852c32f..e9d56784ff6 100644
--- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js
+++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.js
@@ -1,8 +1,6 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* global frappe, refresh_field */
-
frappe.ui.form.on("Supplier Scorecard", {
setup: function(frm) {
if (frm.doc.indicator_color !== "") {
@@ -79,7 +77,7 @@ var loadAllStandings = function(frm) {
callback: function(r) {
for (var j = 0; j < frm.doc.standings.length; j++)
{
- if(!frm.doc.standings[j].hasOwnProperty("standing_name")) {
+ if(!Object.prototype.hasOwnProperty.call(frm.doc.standings[j], "standing_name")) {
frm.get_field("standings").grid.grid_rows[j].remove();
}
}
diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js
index dc5474e3b43..edf0b04d72e 100644
--- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js
+++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js
@@ -1,7 +1,6 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* global frappe, __ */
frappe.listview_settings["Supplier Scorecard"] = {
add_fields: ["indicator_color", "status"],
@@ -14,4 +13,4 @@ frappe.listview_settings["Supplier Scorecard"] = {
}
},
-};
+}
diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js
index 9f8a2dee81d..2186cd89eb3 100644
--- a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js
+++ b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.js
@@ -1,8 +1,6 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* global frappe */
-
frappe.ui.form.on("Supplier Scorecard Criteria", {
refresh: function() {}
});
diff --git a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js
index a4cdeb31957..62079cc3e0b 100644
--- a/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js
+++ b/erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.js
@@ -1,9 +1,6 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* global frappe */
-
-
frappe.ui.form.on("Supplier Scorecard Period", {
onload: function(frm) {
let criteria_grid = frm.get_field("criteria").grid;
diff --git a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js
index dccfcc34bb9..22756e75cf6 100644
--- a/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js
+++ b/erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.js
@@ -1,7 +1,6 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* global frappe */
frappe.ui.form.on("Supplier Scorecard Standing", {
refresh: function() {
diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js
index 2d74fdd190a..b3b4321a887 100644
--- a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js
+++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.js
@@ -1,8 +1,6 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* global frappe */
-
frappe.ui.form.on("Supplier Scorecard Variable", {
refresh: function() {
diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.js b/erpnext/buying/report/procurement_tracker/procurement_tracker.js
index 283d56c9469..416655d6bcc 100644
--- a/erpnext/buying/report/procurement_tracker/procurement_tracker.js
+++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Procurement Tracker"] = {
"filters": [
diff --git a/erpnext/buying/report/purchase_analytics/purchase_analytics.js b/erpnext/buying/report/purchase_analytics/purchase_analytics.js
index a884f06d2c2..a0faa6869a1 100644
--- a/erpnext/buying/report/purchase_analytics/purchase_analytics.js
+++ b/erpnext/buying/report/purchase_analytics/purchase_analytics.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Purchase Analytics"] = {
"filters": [
@@ -81,8 +81,9 @@ frappe.query_reports["Purchase Analytics"] = {
const tree_type = frappe.query_report.filters[0].value;
if (data_doctype != tree_type) return;
- row_name = data[2].content;
- length = data.length;
+ let row_name = data[2].content;
+ let length = data.length;
+ let row_values = '';
if (tree_type == "Supplier") {
row_values = data
@@ -104,7 +105,7 @@ frappe.query_reports["Purchase Analytics"] = {
});
}
- entry = {
+ let entry = {
name: row_name,
values: row_values,
};
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
index 721e54e46f5..91506c0ab39 100644
--- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Purchase Order Analysis"] = {
"filters": [
diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js
index d727584d0aa..cb05109d5b7 100644
--- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Requested Items to Order and Receive"] = {
"filters": [
diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js
index 075671f4ec6..800b8ab7dbd 100644
--- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js
+++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Subcontract Order Summary"] = {
"filters": [
diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js
index 9db769d59bf..35be2a9cf88 100644
--- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js
+++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Subcontracted Item To Be Received"] = {
"filters": [
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js
index 7e5338f353b..33b26dcb5f6 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = {
"filters": [
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 4193b5327d3..79404894cd2 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -56,6 +56,7 @@ from erpnext.stock.get_item_details import (
get_item_tax_map,
get_item_warehouse,
)
+from erpnext.utilities.regional import temporary_flag
from erpnext.utilities.transaction_base import TransactionBase
@@ -760,7 +761,9 @@ class AccountsController(TransactionBase):
}
)
- update_gl_dict_with_regional_fields(self, gl_dict)
+ with temporary_flag("company", self.company):
+ update_gl_dict_with_regional_fields(self, gl_dict)
+
accounting_dimensions = get_accounting_dimensions()
dimension_dict = frappe._dict()
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 3bb11282f1f..5ec24743d9d 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -822,6 +822,15 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(query, filters)
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_doctypes_for_closing(doctype, txt, searchfield, start, page_len, filters):
+ doctypes = frappe.get_hooks("period_closing_doctypes")
+ if txt:
+ doctypes = [d for d in doctypes if txt.lower() in d.lower()]
+ return [(d,) for d in set(doctypes)]
+
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
@@ -865,3 +874,18 @@ def get_fields(doctype, fields=None):
fields.insert(1, meta.title_field.strip())
return unique(fields)
+
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_payment_terms_for_references(doctype, txt, searchfield, start, page_len, filters) -> list:
+ terms = []
+ if filters:
+ terms = frappe.db.get_all(
+ "Payment Schedule",
+ filters={"parent": filters.get("reference")},
+ fields=["payment_term"],
+ limit=page_len,
+ as_list=1,
+ )
+ return terms
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 4661c5ca7e8..62d4c538682 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -18,6 +18,7 @@ from erpnext.controllers.accounts_controller import (
validate_taxes_and_charges,
)
from erpnext.stock.get_item_details import _get_item_tax_template
+from erpnext.utilities.regional import temporary_flag
class calculate_taxes_and_totals(object):
@@ -942,7 +943,6 @@ class calculate_taxes_and_totals(object):
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:
return
- frappe.flags.company = doc.company
# get headers
tax_accounts = []
@@ -952,22 +952,17 @@ def get_itemised_tax_breakup_html(doc):
if tax.description not in tax_accounts:
tax_accounts.append(tax.description)
- headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
-
- # get tax breakup data
- itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
-
- get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes"))
-
- update_itemised_tax_data(doc)
- frappe.flags.company = None
+ with temporary_flag("company", doc.company):
+ headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
+ itemised_tax_data = get_itemised_tax_breakup_data(doc)
+ get_rounded_tax_amount(itemised_tax_data, doc.precision("tax_amount", "taxes"))
+ update_itemised_tax_data(doc)
return frappe.render_template(
"templates/includes/itemised_tax_breakup.html",
dict(
headers=headers,
- itemised_tax=itemised_tax,
- itemised_taxable_amount=itemised_taxable_amount,
+ itemised_tax_data=itemised_tax_data,
tax_accounts=tax_accounts,
doc=doc,
),
@@ -977,10 +972,8 @@ def get_itemised_tax_breakup_html(doc):
@frappe.whitelist()
def get_round_off_applicable_accounts(company, account_list):
# required to set correct region
- frappe.flags.company = company
- account_list = get_regional_round_off_accounts(company, account_list)
-
- return account_list
+ with temporary_flag("company", company):
+ return get_regional_round_off_accounts(company, account_list)
@erpnext.allow_regional
@@ -1005,7 +998,15 @@ def get_itemised_tax_breakup_data(doc):
itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
- return itemised_tax, itemised_taxable_amount
+ itemised_tax_data = []
+ for item_code, taxes in itemised_tax.items():
+ itemised_tax_data.append(
+ frappe._dict(
+ {"item": item_code, "taxable_amount": itemised_taxable_amount.get(item_code), **taxes}
+ )
+ )
+
+ return itemised_tax_data
def get_itemised_tax(taxes, with_tax_account=False):
@@ -1050,9 +1051,10 @@ def get_itemised_taxable_amount(items):
def get_rounded_tax_amount(itemised_tax, precision):
# Rounding based on tax_amount precision
- for taxes in itemised_tax.values():
- for tax_account in taxes:
- taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
+ for taxes in itemised_tax:
+ for row in taxes.values():
+ if isinstance(row, dict) and isinstance(row["tax_amount"], float):
+ row["tax_amount"] = flt(row["tax_amount"], precision)
class init_landed_taxes_and_totals(object):
diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js
index 9ac54183a21..b1799cee6c0 100644
--- a/erpnext/crm/doctype/lead/lead.js
+++ b/erpnext/crm/doctype/lead/lead.js
@@ -54,6 +54,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
}
add_lead_to_prospect () {
+ let me = this;
frappe.prompt([
{
fieldname: 'prospect',
@@ -67,12 +68,12 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
frappe.call({
method: 'erpnext.crm.doctype.lead.lead.add_lead_to_prospect',
args: {
- 'lead': cur_frm.doc.name,
+ 'lead': me.frm.doc.name,
'prospect': data.prospect
},
callback: function(r) {
if (!r.exc) {
- frm.reload_doc();
+ me.frm.reload_doc();
}
},
freeze: true,
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index b2617955a36..6ef82971f5c 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -1,10 +1,10 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-
-{% include 'erpnext/selling/sales_common.js' %}
frappe.provide("erpnext.crm");
+erpnext.pre_sales.set_as_lost("Quotation");
+erpnext.sales_common.setup_selling_controller();
+
-cur_frm.email_field = "contact_email";
frappe.ui.form.on("Opportunity", {
setup: function(frm) {
frm.custom_make_buttons = {
@@ -19,6 +19,8 @@ frappe.ui.form.on("Opportunity", {
}
}
});
+
+ frm.email_field = "contact_email";
},
validate: function(frm) {
@@ -46,10 +48,6 @@ frappe.ui.form.on("Opportunity", {
}
},
- onload_post_render: function(frm) {
- frm.get_field("items").grid.set_multiple_add("item_code", "qty");
- },
-
status:function(frm){
if (frm.doc.status == "Lost"){
frm.trigger('set_as_lost_dialog');
@@ -252,13 +250,13 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller {
onload() {
if(!this.frm.doc.status) {
- frm.set_value('status', 'Open');
+ this.frm.set_value('status', 'Open');
}
if(!this.frm.doc.company && frappe.defaults.get_user_default("Company")) {
- frm.set_value('company', frappe.defaults.get_user_default("Company"));
+ this.frm.set_value('company', frappe.defaults.get_user_default("Company"));
}
if(!this.frm.doc.currency) {
- frm.set_value('currency', frappe.defaults.get_user_default("Currency"));
+ this.frm.set_value('currency', frappe.defaults.get_user_default("Currency"));
}
this.setup_queries();
diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
index fe5707af296..4bf82479a1f 100644
--- a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
+++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["First Response Time for Opportunity"] = {
"filters": [
diff --git a/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js b/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js
index eeb8984513e..d7ff9ad5388 100644
--- a/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js
+++ b/erpnext/crm/report/lead_conversion_time/lead_conversion_time.js
@@ -1,6 +1,6 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Lead Conversion Time"] = {
"filters": [
diff --git a/erpnext/crm/report/lead_details/lead_details.js b/erpnext/crm/report/lead_details/lead_details.js
index 2f6d24224fb..66611f6c6c1 100644
--- a/erpnext/crm/report/lead_details/lead_details.js
+++ b/erpnext/crm/report/lead_details/lead_details.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Lead Details"] = {
"filters": [
diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.js b/erpnext/crm/report/lost_opportunity/lost_opportunity.js
index 927c54df072..8d5923950a6 100644
--- a/erpnext/crm/report/lost_opportunity/lost_opportunity.js
+++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Lost Opportunity"] = {
"filters": [
diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js
index 7cd1710a7f2..0aa21436bc9 100644
--- a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js
+++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Opportunity Summary by Sales Stage"] = {
"filters": [
diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js
index 1426f4b6fd2..31111215229 100644
--- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js
+++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Sales Pipeline Analytics"] = {
"filters": [
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
index 3ba6bb99873..015e943b80a 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
@@ -96,7 +96,7 @@ erpnext.integrations.plaidLink = class plaidLink {
}
onScriptLoaded(me) {
- me.linkHandler = Plaid.create({
+ me.linkHandler = Plaid.create({ // eslint-disable-line no-undef
clientName: me.client_name,
product: me.product,
env: me.plaid_env,
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 316d9437fb8..d8b40e308c4 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -285,10 +285,34 @@ standard_queries = {
"Customer": "erpnext.controllers.queries.customer_query",
}
+period_closing_doctypes = [
+ "Sales Invoice",
+ "Purchase Invoice",
+ "Journal Entry",
+ "Bank Clearance",
+ "Stock Entry",
+ "Dunning",
+ "Invoice Discounting",
+ "Payment Entry",
+ "Period Closing Voucher",
+ "Process Deferred Accounting",
+ "Asset",
+ "Asset Capitalization",
+ "Asset Repair",
+ "Delivery Note",
+ "Landed Cost Voucher",
+ "Purchase Receipt",
+ "Stock Reconciliation",
+ "Subcontracting Receipt",
+]
+
doc_events = {
"*": {
"validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
},
+ tuple(period_closing_doctypes): {
+ "validate": "erpnext.accounts.doctype.accounting_period.accounting_period.validate_accounting_period_on_doc_save",
+ },
"Stock Entry": {
"on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
"on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
@@ -334,6 +358,7 @@ doc_events = {
"erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status",
"erpnext.accounts.doctype.dunning.dunning.resolve_dunning",
],
+ "on_cancel": ["erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
"on_trash": "erpnext.regional.check_deletion_permission",
},
"Address": {
@@ -464,15 +489,6 @@ advance_payment_doctypes = ["Sales Order", "Purchase Order"]
invoice_doctypes = ["Sales Invoice", "Purchase Invoice"]
-period_closing_doctypes = [
- "Sales Invoice",
- "Purchase Invoice",
- "Journal Entry",
- "Bank Clearance",
- "Asset",
- "Stock Entry",
-]
-
bank_reconciliation_doctypes = [
"Payment Entry",
"Journal Entry",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index 8e9f5423622..f1e60948130 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -344,6 +344,28 @@ frappe.ui.form.on('Job Card', {
if(frm.doc.__islocal)
return;
+ function setCurrentIncrement() {
+ currentIncrement += 1;
+ return currentIncrement;
+ }
+
+ function updateStopwatch(increment) {
+ var hours = Math.floor(increment / 3600);
+ var minutes = Math.floor((increment - (hours * 3600)) / 60);
+ var seconds = increment - (hours * 3600) - (minutes * 60);
+
+ $(section).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString());
+ $(section).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString());
+ $(section).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString());
+ }
+
+ function initialiseTimer() {
+ const interval = setInterval(function() {
+ var current = setCurrentIncrement();
+ updateStopwatch(current);
+ }, 1000);
+ }
+
frm.dashboard.refresh();
const timer = `
", can_edit_accounts_after)
+
+ all_dunnings = frappe.get_all("Dunning", filters=filters, pluck="name")
+
+ for dunning_name in all_dunnings:
+ dunning = frappe.get_doc("Dunning", dunning_name)
+ if not dunning.sales_invoice:
+ # nothing we can do
+ continue
+
+ if dunning.overdue_payments:
+ # something's already here, doesn't need patching
+ continue
+
+ payment_schedules = frappe.get_all(
+ "Payment Schedule",
+ filters={"parent": dunning.sales_invoice},
+ fields=[
+ "parent as sales_invoice",
+ "name as payment_schedule",
+ "payment_term",
+ "due_date",
+ "invoice_portion",
+ "payment_amount",
+ # at the time of creating this dunning, the full amount was outstanding
+ "payment_amount as outstanding",
+ "'0' as paid_amount",
+ "discounted_amount",
+ ],
+ )
+
+ dunning.extend("overdue_payments", payment_schedules)
+ dunning.validate()
+
+ dunning.flags.ignore_validate_update_after_submit = True
+ dunning.save()
+
+ # Reverse entries only if dunning is submitted and not resolved
+ if dunning.docstatus == 1 and dunning.status != "Resolved":
+ # With the new logic, dunning amount gets recorded as additional income
+ # at time of payment. We don't want to record the dunning amount twice,
+ # so we reverse previous GL Entries that recorded the dunning amount at
+ # time of submission of the Dunning.
+ make_reverse_gl_entries(voucher_type="Dunning", voucher_no=dunning.name)
+
+
+def get_accounts_closing_date():
+ """Get the date when accounts were frozen/closed"""
+ accounts_frozen_till = frappe.db.get_single_value(
+ "Accounts Settings", "acc_frozen_upto"
+ ) # always returns datetime.date
+
+ period_closing_date = frappe.db.get_value(
+ "Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc"
+ )
+
+ # Set most recent frozen/closing date as filter
+ if accounts_frozen_till and period_closing_date:
+ can_edit_accounts_after = max(accounts_frozen_till, period_closing_date)
+ else:
+ can_edit_accounts_after = accounts_frozen_till or period_closing_date
+
+ return can_edit_accounts_after
diff --git a/erpnext/patches/v14_0/update_closing_balances.py b/erpnext/patches/v14_0/update_closing_balances.py
index 2947b98740b..2c842814839 100644
--- a/erpnext/patches/v14_0/update_closing_balances.py
+++ b/erpnext/patches/v14_0/update_closing_balances.py
@@ -69,7 +69,6 @@ def execute():
entries = gl_entries + closing_entries
- if entries:
- make_closing_entries(entries, voucher_name=pcv.name)
- i += 1
- company_wise_order[pcv.company].append(pcv.posting_date)
+ make_closing_entries(entries, pcv.name, pcv.company, pcv.posting_date)
+ company_wise_order[pcv.company].append(pcv.posting_date)
+ i += 1
diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js
index 5aa44c0a8c9..fa70b9394a0 100644
--- a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js
+++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Delayed Tasks Summary"] = {
"filters": [
diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js
index 13f49ed6bed..8566b1fc781 100644
--- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js
+++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Employee Billing Summary"] = {
"filters": [
diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.js b/erpnext/projects/report/project_billing_summary/project_billing_summary.js
index caac1d86b45..0242036dc72 100644
--- a/erpnext/projects/report/project_billing_summary/project_billing_summary.js
+++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Project Billing Summary"] = {
"filters": [
diff --git a/erpnext/projects/report/project_summary/project_summary.js b/erpnext/projects/report/project_summary/project_summary.js
index 414b7b206a1..21dbfda73f9 100644
--- a/erpnext/projects/report/project_summary/project_summary.js
+++ b/erpnext/projects/report/project_summary/project_summary.js
@@ -1,6 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-/* eslint-disable */
+
frappe.query_reports["Project Summary"] = {
"filters": [
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 1bed5418318..b9b48aba453 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -26,7 +26,6 @@
"public/js/templates/item_selector.html",
"public/js/utils/item_selector.js",
"public/js/help_links.js",
- "public/js/agriculture/ternary_plot.js",
"public/js/templates/item_quick_entry.html",
"public/js/utils/customer_quick_entry.js",
"public/js/utils/supplier_quick_entry.js",
diff --git a/erpnext/public/js/account_tree_grid.js b/erpnext/public/js/account_tree_grid.js
deleted file mode 100644
index 413a5ee9719..00000000000
--- a/erpnext/public/js/account_tree_grid.js
+++ /dev/null
@@ -1,238 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see
.
-
-erpnext.AccountTreeGrid = class AccountTreeGrid extends frappe.views.TreeGridReport {
- constructor(wrapper, title) {
- super({
- title: title,
- parent: $(wrapper).find('.layout-main'),
- page: wrapper.page,
- doctypes: ["Company", "Fiscal Year", "Account", "GL Entry", "Cost Center"],
- tree_grid: {
- show: true,
- parent_field: "parent_account",
- formatter: function(item) {
- return repl("
\
- %(value)s", {
- value: item.name,
- });
- }
- },
- });
-
- this.filters = [
- {fieldtype: "Select", label: __("Company"), link:"Company", fieldname: "company",
- default_value: __("Select Company..."),
- filter: function(val, item, opts, me) {
- if (item.company == val || val == opts.default_value) {
- return me.apply_zero_filter(val, item, opts, me);
- }
- return false;
- }},
- {fieldtype: "Select", label: "Fiscal Year", link:"Fiscal Year", fieldname: "fiscal_year",
- default_value: __("Select Fiscal Year...")},
- {fieldtype: "Date", label: __("From Date"), fieldname: "from_date"},
- {fieldtype: "Label", label: __("To")},
- {fieldtype: "Date", label: __("To Date"), fieldname: "to_date"}
- ]
- }
- setup_columns() {
- this.columns = [
- {id: "name", name: __("Account"), field: "name", width: 300, cssClass: "cell-title"},
- {id: "opening_dr", name: __("Opening (Dr)"), field: "opening_dr", width: 100,
- formatter: this.currency_formatter},
- {id: "opening_cr", name: __("Opening (Cr)"), field: "opening_cr", width: 100,
- formatter: this.currency_formatter},
- {id: "debit", name: __("Debit"), field: "debit", width: 100,
- formatter: this.currency_formatter},
- {id: "credit", name: __("Credit"), field: "credit", width: 100,
- formatter: this.currency_formatter},
- {id: "closing_dr", name: __("Closing (Dr)"), field: "closing_dr", width: 100,
- formatter: this.currency_formatter},
- {id: "closing_cr", name: __("Closing (Cr)"), field: "closing_cr", width: 100,
- formatter: this.currency_formatter}
- ];
- }
-
- setup_filters() {
- super.setup_filters();
- var me = this;
- // default filters
- this.filter_inputs.fiscal_year.change(function() {
- var fy = $(this).val();
- $.each(frappe.report_dump.data["Fiscal Year"], function(i, v) {
- if (v.name==fy) {
- me.filter_inputs.from_date.val(frappe.datetime.str_to_user(v.year_start_date));
- me.filter_inputs.to_date.val(frappe.datetime.str_to_user(v.year_end_date));
- }
- });
- me.refresh();
- });
- me.show_zero_check()
- if(me.ignore_closing_entry) me.ignore_closing_entry();
- }
- prepare_data() {
- var me = this;
- if(!this.primary_data) {
- // make accounts list
- me.data = [];
- me.parent_map = {};
- me.item_by_name = {};
-
- $.each(frappe.report_dump.data["Account"], function(i, v) {
- var d = copy_dict(v);
-
- me.data.push(d);
- me.item_by_name[d.name] = d;
- if(d.parent_account) {
- me.parent_map[d.name] = d.parent_account;
- }
- });
-
- me.primary_data = [].concat(me.data);
- }
-
- me.data = [].concat(me.primary_data);
- $.each(me.data, function(i, d) {
- me.init_account(d);
- });
-
- this.set_indent();
- this.prepare_balances();
-
- }
- init_account(d) {
- this.reset_item_values(d);
- }
-
- prepare_balances() {
- var gl = frappe.report_dump.data['GL Entry'];
- var me = this;
-
- this.opening_date = frappe.datetime.user_to_obj(this.filter_inputs.from_date.val());
- this.closing_date = frappe.datetime.user_to_obj(this.filter_inputs.to_date.val());
- this.set_fiscal_year();
- if (!this.fiscal_year) return;
-
- $.each(this.data, function(i, v) {
- v.opening_dr = v.opening_cr = v.debit
- = v.credit = v.closing_dr = v.closing_cr = 0;
- });
-
- $.each(gl, function(i, v) {
- var posting_date = frappe.datetime.str_to_obj(v.posting_date);
- var account = me.item_by_name[v.account];
- me.update_balances(account, posting_date, v);
- });
-
- this.update_groups();
- }
- update_balances(account, posting_date, v) {
- // opening
- if (posting_date < this.opening_date || v.is_opening === "Yes") {
- if (account.report_type === "Profit and Loss" &&
- posting_date <= frappe.datetime.str_to_obj(this.fiscal_year[1])) {
- // balance of previous fiscal_year should
- // not be part of opening of pl account balance
- } else {
- var opening_bal = flt(account.opening_dr) - flt(account.opening_cr) +
- flt(v.debit) - flt(v.credit);
- this.set_debit_or_credit(account, "opening", opening_bal);
- }
- } else if (this.opening_date <= posting_date && posting_date <= this.closing_date) {
- // in between
- account.debit += flt(v.debit);
- account.credit += flt(v.credit);
- }
- // closing
- var closing_bal = flt(account.opening_dr) - flt(account.opening_cr) +
- flt(account.debit) - flt(account.credit);
- this.set_debit_or_credit(account, "closing", closing_bal);
- }
- set_debit_or_credit(account, field, balance) {
- if(balance > 0) {
- account[field+"_dr"] = balance;
- account[field+"_cr"] = 0;
- } else {
- account[field+"_cr"] = Math.abs(balance);
- account[field+"_dr"] = 0;
- }
- }
- update_groups() {
- // update groups
- var me= this;
- $.each(this.data, function(i, account) {
- // update groups
- if((account.is_group == 0) || (account.rgt - account.lft == 1)) {
- var parent = me.parent_map[account.name];
- while(parent) {
- var parent_account = me.item_by_name[parent];
- $.each(me.columns, function(c, col) {
- if (col.formatter == me.currency_formatter) {
- if(col.field=="opening_dr") {
- var bal = flt(parent_account.opening_dr) -
- flt(parent_account.opening_cr) +
- flt(account.opening_dr) - flt(account.opening_cr);
- me.set_debit_or_credit(parent_account, "opening", bal);
- } else if(col.field=="closing_dr") {
- var bal = flt(parent_account.closing_dr) -
- flt(parent_account.closing_cr) +
- flt(account.closing_dr) - flt(account.closing_cr);
- me.set_debit_or_credit(parent_account, "closing", bal);
- } else if(in_list(["debit", "credit"], col.field)) {
- parent_account[col.field] = flt(parent_account[col.field]) +
- flt(account[col.field]);
- }
- }
- });
- parent = me.parent_map[parent];
- }
- }
- });
- }
-
- set_fiscal_year() {
- if (this.opening_date > this.closing_date) {
- frappe.msgprint(__("Opening Date should be before Closing Date"));
- return;
- }
-
- this.fiscal_year = null;
- var me = this;
- $.each(frappe.report_dump.data["Fiscal Year"], function(i, v) {
- if (me.opening_date >= frappe.datetime.str_to_obj(v.year_start_date) &&
- me.closing_date <= frappe.datetime.str_to_obj(v.year_end_date)) {
- me.fiscal_year = v;
- }
- });
-
- if (!this.fiscal_year) {
- frappe.msgprint(__("Opening Date and Closing Date should be within same Fiscal Year"));
- return;
- }
- }
-
- show_general_ledger(account) {
- frappe.route_options = {
- account: account,
- company: this.company,
- from_date: this.from_date,
- to_date: this.to_date
- };
- frappe.set_route("query-report", "General Ledger");
- }
-};
diff --git a/erpnext/public/js/agriculture/ternary_plot.js b/erpnext/public/js/agriculture/ternary_plot.js
deleted file mode 100644
index b06a1fd7c8c..00000000000
--- a/erpnext/public/js/agriculture/ternary_plot.js
+++ /dev/null
@@ -1,232 +0,0 @@
-frappe.provide('agriculture');
-
-agriculture.TernaryPlot = class TernaryPlot {
- constructor(opts) {
- Object.assign(this, opts);
-
- frappe.require('assets/frappe/js/lib/snap.svg-min.js', () => {
- this.make_svg();
- this.init_snap();
- this.init_config();
- this.make_plot();
- this.make_plot_marking();
- this.make_legend();
- this.mark_blip();
- });
- }
-
- make_svg() {
- this.$svg = $('