Merge pull request #35689 from marination/payments-based-dunning
feat: Payments based dunning
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index 9b4db49..2ce1125 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -7,11 +7,9 @@
- '**.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/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json b/erpnext/accounts/dashboard_chart/budget_variance/budget_variance.json
index 8631d3d..4883106 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 3fa995b..25caa44 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 9540084..e75af70 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 @@
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 e3d805a..f17b6f9 100644
--- a/erpnext/accounts/doctype/accounting_period/accounting_period.js
+++ b/erpnext/accounts/doctype/accounting_period/accounting_period.js
@@ -20,5 +20,11 @@
}
});
}
+
+ 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 80c9715..d5f37a6 100644
--- a/erpnext/accounts/doctype/accounting_period/accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py
@@ -11,6 +11,10 @@
pass
+class ClosedAccountingPeriod(frappe.ValidationError):
+ pass
+
+
class AccountingPeriod(Document):
def validate(self):
self.validate_overlap()
@@ -65,3 +69,42 @@
"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 85025d1..41d9479 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 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 @@
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/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 79f0419..7542bab 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -226,10 +226,12 @@
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))
+ latest = (latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()).get(
+ d.payment_term
+ )
# The reference has already been fully paid
if not latest:
@@ -251,6 +253,18 @@
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 +1514,9 @@
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 +1576,27 @@
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 +1613,14 @@
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 +1632,7 @@
"posting_date": d.posting_date,
"invoice_amount": flt(d.invoice_amount),
"outstanding_amount": flt(d.outstanding_amount),
+ "payment_term_outstanding": payment_term_outstanding,
"payment_amount": payment_term.payment_amount,
"payment_term": payment_term.payment_term,
"account": d.account,
@@ -2372,6 +2416,7 @@
"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/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index 641f452..922722f 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -133,6 +133,8 @@
gl_entries=gl_entries,
closing_entries=closing_entries,
voucher_name=self.name,
+ company=self.company,
+ closing_date=self.posting_date,
queue="long",
)
frappe.msgprint(
@@ -140,7 +142,7 @@
alert=True,
)
else:
- process_gl_entries(gl_entries, closing_entries, voucher_name=self.name)
+ 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,7 +323,7 @@
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,
)
@@ -329,7 +331,7 @@
try:
make_gl_entries(gl_entries, merge_entries=False)
- make_closing_entries(gl_entries + closing_entries, voucher_name=voucher_name)
+ make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
frappe.db.set_value(
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
)
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index f1dad87..e9dc5fc 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -13,14 +13,11 @@
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/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 5176c31..39917f9 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -221,7 +221,10 @@
)
else:
if start_date:
- opening_balance = opening_balance.where(closing_balance.posting_date >= start_date)
+ opening_balance = opening_balance.where(
+ (closing_balance.posting_date >= start_date)
+ & (closing_balance.posting_date < filters.from_date)
+ )
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
else:
opening_balance = opening_balance.where(
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 6452ed2..751796b 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 6f7da8e..f6b9717 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/controllers/queries.py b/erpnext/controllers/queries.py
index 3bb1128..d1dcd6a 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -824,6 +824,15 @@
@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):
item_doc = frappe.get_cached_doc("Item", filters.get("item_code"))
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index b68318a..d8b40e3 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -285,10 +285,34 @@
"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",
@@ -465,15 +489,6 @@
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/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index a988bad..d8cc8f6 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -1539,7 +1539,7 @@
frappe.qb.from_(table)
.inner_join(child)
.on(table.name == child.parent)
- .select(Sum(child.required_bom_qty * IfNull(child.conversion_factor, 1.0)))
+ .select(Sum(child.quantity * IfNull(child.conversion_factor, 1.0)))
.where(
(table.docstatus == 1)
& (child.item_code == item_code)
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index fcfba7f..f60dbfc 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -933,6 +933,54 @@
self.assertEqual(after_qty, before_qty)
+ def test_resered_qty_for_production_plan_for_material_requests_with_multi_UOM(self):
+ from erpnext.stock.utils import get_or_make_bin
+
+ fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
+ bom_item = make_item(
+ properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"}
+ ).name
+
+ if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}):
+ doc = frappe.get_doc("Item", bom_item)
+ doc.append("uoms", {"uom": "Nos", "conversion_factor": 25})
+ doc.save()
+
+ make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
+
+ bin_name = get_or_make_bin(bom_item, "_Test Warehouse - _TC")
+ before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+
+ pln = create_production_plan(
+ item_code=fg_item, planned_qty=100, ignore_existing_ordered_qty=1, stock_uom="_Test UOM 1"
+ )
+
+ for row in pln.mr_items:
+ self.assertEqual(row.uom, "Nos")
+ self.assertEqual(row.quantity, 4)
+
+ reserved_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+ self.assertEqual(reserved_qty - before_qty, 100.0)
+
+ pln.submit_material_request = 1
+ pln.make_work_order()
+
+ for work_order in frappe.get_all(
+ "Work Order",
+ fields=["name"],
+ filters={"production_plan": pln.name},
+ ):
+ wo_doc = frappe.get_doc("Work Order", work_order.name)
+ wo_doc.source_warehouse = "_Test Warehouse - _TC"
+ wo_doc.wip_warehouse = "_Test Warehouse 1 - _TC"
+ wo_doc.fg_warehouse = "_Test Warehouse - _TC"
+ wo_doc.submit()
+
+ reserved_qty_after_mr = flt(
+ frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan")
+ )
+ self.assertEqual(reserved_qty_after_mr, before_qty)
+
def test_skip_available_qty_for_sub_assembly_items(self):
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
diff --git a/erpnext/patches/v14_0/update_closing_balances.py b/erpnext/patches/v14_0/update_closing_balances.py
index 2947b98..2c84281 100644
--- a/erpnext/patches/v14_0/update_closing_balances.py
+++ b/erpnext/patches/v14_0/update_closing_balances.py
@@ -69,7 +69,6 @@
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/patches/v15_0/remove_exotel_integration.py b/erpnext/patches/v15_0/remove_exotel_integration.py
index a37773f..9b99fc6 100644
--- a/erpnext/patches/v15_0/remove_exotel_integration.py
+++ b/erpnext/patches/v15_0/remove_exotel_integration.py
@@ -1,5 +1,3 @@
-from contextlib import suppress
-
import click
import frappe
from frappe import _
@@ -13,12 +11,14 @@
if "exotel_integration" in frappe.get_installed_apps():
return
- with suppress(Exception):
+ try:
exotel = frappe.get_doc(SETTINGS_DOCTYPE)
if exotel.enabled:
notify_existing_users()
frappe.delete_doc("DocType", SETTINGS_DOCTYPE)
+ except Exception:
+ frappe.log_error("Failed to remove Exotel Integration.")
def notify_existing_users():
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 497f8d2..cc03eca 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -401,6 +401,10 @@
},
get_fiscal_year: function(date) {
+ if(!date) {
+ date = frappe.datetime.get_today();
+ }
+
let fiscal_year = '';
frappe.call({
method: "erpnext.accounts.utils.get_fiscal_year",
diff --git a/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json b/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json
index 914d915..2f668a8 100644
--- a/erpnext/selling/dashboard_chart/sales_order_trends/sales_order_trends.json
+++ b/erpnext/selling/dashboard_chart/sales_order_trends/sales_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\",\"based_on\":\"Item\"}",
- "idx": 0,
+ "idx": 1,
"is_public": 1,
"is_standard": 1,
- "modified": "2020-07-22 16:24:45.726270",
+ "modified": "2023-07-19 13:09:45.341791",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Trends",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Sales Order Trends",
+ "roles": [],
"timeseries": 0,
"type": "Line",
"use_report_chart": 1,
diff --git a/erpnext/selling/dashboard_chart/top_customers/top_customers.json b/erpnext/selling/dashboard_chart/top_customers/top_customers.json
index 59a2ba3..2972980 100644
--- a/erpnext/selling/dashboard_chart/top_customers/top_customers.json
+++ b/erpnext/selling/dashboard_chart/top_customers/top_customers.json
@@ -5,18 +5,19 @@
"custom_options": "",
"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\":\"Yearly\",\"based_on\":\"Customer\"}",
"idx": 0,
"is_public": 1,
"is_standard": 1,
- "modified": "2020-07-22 17:03:10.320147",
+ "modified": "2023-07-19 13:14:20.151502",
"modified_by": "Administrator",
"module": "Selling",
"name": "Top Customers",
"number_of_groups": 0,
"owner": "Administrator",
"report_name": "Delivery Note Trends",
+ "roles": [],
"timeseries": 0,
"type": "Bar",
"use_report_chart": 1,
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 45100d7..796e258 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1904,12 +1904,11 @@
"voucher_no": so.name,
"voucher_detail_no": item.name,
},
- fields=["status", "reserved_qty", "delivered_qty"],
+ fields=["reserved_qty", "delivered_qty"],
)
for sre_detail in sre_details:
self.assertEqual(sre_detail.reserved_qty, sre_detail.delivered_qty)
- self.assertEqual(sre_detail.status, "Delivered")
def test_delivered_item_material_request(self):
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index ee247fd..00b1b20 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -118,8 +118,8 @@
self.title = _("{0} Request for {1}").format(_(self.material_request_type), items)[:100]
def on_submit(self):
- self.update_requested_qty()
self.update_requested_qty_in_production_plan()
+ self.update_requested_qty()
if self.material_request_type == "Purchase":
self.validate_budget()
@@ -178,8 +178,8 @@
)
def on_cancel(self):
- self.update_requested_qty()
self.update_requested_qty_in_production_plan()
+ self.update_requested_qty()
def get_mr_items_ordered_qty(self, mr_items):
mr_items_ordered_qty = {}
@@ -270,7 +270,13 @@
item_wh_list.append([d.item_code, d.warehouse])
for item_code, warehouse in item_wh_list:
- update_bin_qty(item_code, warehouse, {"indented_qty": get_indented_qty(item_code, warehouse)})
+ update_bin_qty(
+ item_code,
+ warehouse,
+ {
+ "indented_qty": get_indented_qty(item_code, warehouse),
+ },
+ )
def update_requested_qty_in_production_plan(self):
production_plans = []
diff --git a/erpnext/templates/includes/itemised_tax_breakup.html b/erpnext/templates/includes/itemised_tax_breakup.html
index 5652bb1..fbc80de 100644
--- a/erpnext/templates/includes/itemised_tax_breakup.html
+++ b/erpnext/templates/includes/itemised_tax_breakup.html
@@ -15,7 +15,7 @@
{% for item, taxes in itemised_tax.items() %}
<tr>
<td>{{ item }}</td>
- <td class='text-right'>
+ <td class="text-right">
{% if doc.get('is_return') %}
{{ frappe.utils.fmt_money((itemised_taxable_amount.get(item, 0))|abs, None, doc.currency) }}
{% else %}
@@ -25,7 +25,7 @@
{% for tax_account in tax_accounts %}
{% set tax_details = taxes.get(tax_account) %}
{% if tax_details %}
- <td class='text-right'>
+ <td class="text-right">
{% if tax_details.tax_rate or not tax_details.tax_amount %}
({{ tax_details.tax_rate }}%)
{% endif %}