diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js
index 63cc46518ff..7e57c2fc471 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js
@@ -4,6 +4,23 @@
frappe.ui.form.on("Bank Clearance", {
setup: function(frm) {
frm.add_fetch("account", "account_currency", "account_currency");
+
+ frm.set_query("account", function() {
+ return {
+ "filters": {
+ "account_type": ["in",["Bank","Cash"]],
+ "is_group": 0,
+ }
+ };
+ });
+
+ frm.set_query("bank_account", function () {
+ return {
+ filters: {
+ 'is_company_account': 1
+ },
+ };
+ });
},
onload: function(frm) {
@@ -12,14 +29,7 @@ frappe.ui.form.on("Bank Clearance", {
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "";
frm.set_value("account", default_bank_account);
- frm.set_query("account", function() {
- return {
- "filters": {
- "account_type": ["in",["Bank","Cash"]],
- "is_group": 0
- }
- };
- });
+
frm.set_value("from_date", frappe.datetime.month_start());
frm.set_value("to_date", frappe.datetime.month_end());
diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py
index 6ac3350c3b0..637ac7a04ce 100644
--- a/erpnext/accounts/doctype/budget/budget.py
+++ b/erpnext/accounts/doctype/budget/budget.py
@@ -107,7 +107,7 @@ class Budget(Document):
self.naming_series = f"{{{frappe.scrub(self.budget_against)}}}./.{self.fiscal_year}/.###"
-def validate_expense_against_budget(args):
+def validate_expense_against_budget(args, expense_amount=0):
args = frappe._dict(args)
if args.get("company") and not args.fiscal_year:
@@ -175,13 +175,13 @@ def validate_expense_against_budget(args):
) # nosec
if budget_records:
- validate_budget_records(args, budget_records)
+ validate_budget_records(args, budget_records, expense_amount)
-def validate_budget_records(args, budget_records):
+def validate_budget_records(args, budget_records, expense_amount):
for budget in budget_records:
if flt(budget.budget_amount):
- amount = get_amount(args, budget)
+ amount = expense_amount or get_amount(args, budget)
yearly_action, monthly_action = get_actions(args, budget)
if monthly_action in ["Stop", "Warn"]:
diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py
index c48c7d97a2a..11af9a29f6f 100644
--- a/erpnext/accounts/doctype/budget/test_budget.py
+++ b/erpnext/accounts/doctype/budget/test_budget.py
@@ -334,6 +334,39 @@ class TestBudget(unittest.TestCase):
budget.cancel()
jv.cancel()
+ def test_monthly_budget_against_main_cost_center(self):
+ from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+ from erpnext.accounts.doctype.cost_center_allocation.test_cost_center_allocation import (
+ create_cost_center_allocation,
+ )
+
+ cost_centers = [
+ "Main Budget Cost Center 1",
+ "Sub Budget Cost Center 1",
+ "Sub Budget Cost Center 2",
+ ]
+
+ for cc in cost_centers:
+ create_cost_center(cost_center_name=cc, company="_Test Company")
+
+ create_cost_center_allocation(
+ "_Test Company",
+ "Main Budget Cost Center 1 - _TC",
+ {"Sub Budget Cost Center 1 - _TC": 60, "Sub Budget Cost Center 2 - _TC": 40},
+ )
+
+ make_budget(budget_against="Cost Center", cost_center="Main Budget Cost Center 1 - _TC")
+
+ jv = make_journal_entry(
+ "_Test Account Cost for Goods Sold - _TC",
+ "_Test Bank - _TC",
+ 400000,
+ "Main Budget Cost Center 1 - _TC",
+ posting_date=nowdate(),
+ )
+
+ self.assertRaises(BudgetError, jv.submit)
+
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "project":
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 763e2e6992c..a5ff7f1aa79 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -312,8 +312,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
}
}
- get_outstanding(doctype, docname, company, child, due_date) {
- var me = this;
+ get_outstanding(doctype, docname, company, child) {
var args = {
"doctype": doctype,
"docname": docname,
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 52690e1e662..de012b28ec8 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -1210,6 +1210,7 @@ def get_outstanding(args):
args = json.loads(args)
company_currency = erpnext.get_company_currency(args.get("company"))
+ due_date = None
if args.get("doctype") == "Journal Entry":
condition = " and party=%(party)s" if args.get("party") else ""
@@ -1234,10 +1235,12 @@ def get_outstanding(args):
invoice = frappe.db.get_value(
args["doctype"],
args["docname"],
- ["outstanding_amount", "conversion_rate", scrub(party_type)],
+ ["outstanding_amount", "conversion_rate", scrub(party_type), "due_date"],
as_dict=1,
)
+ due_date = invoice.get("due_date")
+
exchange_rate = (
invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
)
@@ -1260,6 +1263,7 @@ def get_outstanding(args):
"exchange_rate": exchange_rate,
"party_type": party_type,
"party": invoice.get(scrub(party_type)),
+ "reference_due_date": due_date,
}
diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
index a0ea43332c9..47ad19e0f98 100644
--- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
+++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
@@ -216,7 +216,7 @@
{
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
"fieldname": "reference_due_date",
- "fieldtype": "Select",
+ "fieldtype": "Date",
"label": "Reference Due Date",
"no_copy": 1
},
@@ -284,7 +284,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-10-13 17:07:17.999191",
+ "modified": "2022-10-26 20:03:10.906259",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
index 7eb5c4234d1..1f4166151a1 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
@@ -22,13 +22,13 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
}
if (data.user != frappe.session.user) return;
if (data.count == data.total) {
- setTimeout((title) => {
+ setTimeout(() => {
frm.doc.import_in_progress = false;
frm.clear_table("invoices");
frm.refresh_fields();
frm.page.clear_indicator();
- frm.dashboard.hide_progress(title);
- frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
+ frm.dashboard.hide_progress();
+ frappe.msgprint(__("Opening {0} Invoices created", [frm.doc.invoice_type]));
}, 1500, data.title);
return;
}
@@ -51,13 +51,6 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
method: "make_invoices",
freeze: 1,
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
- callback: function(r) {
- if (r.message.length == 1) {
- frappe.msgprint(__("{0} Invoice created successfully.", [frm.doc.invoice_type]));
- } else if (r.message.length < 50) {
- frappe.msgprint(__("{0} Invoices created successfully.", [frm.doc.invoice_type]));
- }
- }
});
});
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index 99377421c58..c566b420ba8 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -257,8 +257,6 @@ def start_import(invoices):
def publish(index, total, doctype):
- if total < 50:
- return
frappe.publish_realtime(
"opening_invoice_creation_progress",
dict(
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index 6e7ebd1414d..ce9ce647db0 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -52,7 +52,10 @@
"free_item_rate",
"column_break_42",
"free_item_uom",
+ "round_free_qty",
"is_recursive",
+ "recurse_for",
+ "apply_recursion_over",
"section_break_23",
"valid_from",
"valid_upto",
@@ -578,12 +581,34 @@
"fieldtype": "Select",
"label": "Naming Series",
"options": "PRLE-.####"
+ },
+ {
+ "default": "0",
+ "fieldname": "round_free_qty",
+ "fieldtype": "Check",
+ "label": "Round Free Qty"
+ },
+ {
+ "depends_on": "is_recursive",
+ "description": "Give free item for every N quantity",
+ "fieldname": "recurse_for",
+ "fieldtype": "Float",
+ "label": "Recurse Every (As Per Transaction UOM)",
+ "mandatory_depends_on": "is_recursive"
+ },
+ {
+ "default": "0",
+ "depends_on": "is_recursive",
+ "description": "Qty for which recursion isn't applicable.",
+ "fieldname": "apply_recursion_over",
+ "fieldtype": "Float",
+ "label": "Apply Recursion Over (As Per Transaction UOM)"
}
],
"icon": "fa fa-gift",
"idx": 1,
"links": [],
- "modified": "2022-09-16 16:00:38.356266",
+ "modified": "2022-10-13 19:05:35.056304",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 826d71b12e9..ed46d85e3a4 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -24,6 +24,7 @@ class PricingRule(Document):
self.validate_applicable_for_selling_or_buying()
self.validate_min_max_amt()
self.validate_min_max_qty()
+ self.validate_recursion()
self.cleanup_fields_value()
self.validate_rate_or_discount()
self.validate_max_discount()
@@ -109,6 +110,18 @@ class PricingRule(Document):
if self.min_amt and self.max_amt and flt(self.min_amt) > flt(self.max_amt):
throw(_("Min Amt can not be greater than Max Amt"))
+ def validate_recursion(self):
+ if self.price_or_product_discount != "Product":
+ return
+ if self.free_item or self.same_item:
+ if flt(self.recurse_for) <= 0:
+ self.recurse_for = 1
+ if self.is_recursive:
+ if flt(self.apply_recursion_over) > flt(self.min_qty):
+ throw(_("Min Qty should be greater than Recurse Over Qty"))
+ if flt(self.apply_recursion_over) < 0:
+ throw(_("Recurse Over Qty cannot be less than 0"))
+
def cleanup_fields_value(self):
for logic_field in ["apply_on", "applicable_for", "rate_or_discount"]:
fieldname = frappe.scrub(self.get(logic_field) or "")
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index fbe567824f2..79c7c12b413 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -943,6 +943,45 @@ class TestPricingRule(unittest.TestCase):
si.delete()
rule.delete()
+ def test_pricing_rule_for_product_free_item_rounded_qty_and_recursion(self):
+ frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
+ test_record = {
+ "doctype": "Pricing Rule",
+ "title": "_Test Pricing Rule",
+ "apply_on": "Item Code",
+ "currency": "USD",
+ "items": [
+ {
+ "item_code": "_Test Item",
+ }
+ ],
+ "selling": 1,
+ "rate": 0,
+ "min_qty": 3,
+ "max_qty": 7,
+ "price_or_product_discount": "Product",
+ "same_item": 1,
+ "free_qty": 1,
+ "round_free_qty": 1,
+ "is_recursive": 1,
+ "recurse_for": 2,
+ "company": "_Test Company",
+ }
+ frappe.get_doc(test_record.copy()).insert()
+
+ # With pricing rule
+ so = make_sales_order(item_code="_Test Item", qty=5)
+ so.load_from_db()
+ self.assertEqual(so.items[1].is_free_item, 1)
+ self.assertEqual(so.items[1].item_code, "_Test Item")
+ self.assertEqual(so.items[1].qty, 2)
+
+ so = make_sales_order(item_code="_Test Item", qty=7)
+ so.load_from_db()
+ self.assertEqual(so.items[1].is_free_item, 1)
+ self.assertEqual(so.items[1].item_code, "_Test Item")
+ self.assertEqual(so.items[1].qty, 4)
+
test_dependencies = ["Campaign"]
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 4c78d7261d0..35eed49da70 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -627,9 +627,13 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
qty = pricing_rule.free_qty or 1
if pricing_rule.is_recursive:
- transaction_qty = args.get("qty") if args else doc.total_qty
+ transaction_qty = (
+ args.get("qty") if args else doc.total_qty
+ ) - pricing_rule.apply_recursion_over
if transaction_qty:
- qty = flt(transaction_qty) * qty
+ qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
+ if pricing_rule.round_free_qty:
+ qty = round(qty)
free_item_data_args = {
"item_code": free_item,
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index c3a9855ff44..39a623519a6 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -569,6 +569,10 @@ frappe.ui.form.on("Purchase Invoice", {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
+
+ if (frm.is_new()) {
+ frm.clear_table("tax_withheld_vouchers");
+ }
},
is_subcontracted: function(frm) {
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 3d74b8f139b..882a374046d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -71,6 +71,9 @@ class PurchaseInvoice(BuyingController):
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
self.set_onload("supplier_tds", supplier_tds)
+ if self.is_new():
+ self.set("tax_withheld_vouchers", [])
+
def before_save(self):
if not self.on_hold:
self.release_date = ""
@@ -1415,7 +1418,7 @@ class PurchaseInvoice(BuyingController):
"Stock Ledger Entry",
"Repost Item Valuation",
"Payment Ledger Entry",
- "Purchase Invoice",
+ "Tax Withheld Vouchers",
)
self.update_advance_tax_references(cancel=1)
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 9de90368871..a8f6f80b6b9 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -214,6 +214,7 @@
"reqd": 1
},
{
+ "default": "1",
"depends_on": "eval:doc.uom != doc.stock_uom",
"fieldname": "conversion_factor",
"fieldtype": "Float",
@@ -820,6 +821,7 @@
},
{
"collapsible": 1,
+ "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"fieldname": "section_break_26",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -871,7 +873,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-10-12 03:37:29.032732",
+ "modified": "2022-10-26 16:05:37.304788",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 1ba782451b4..cb0d1a75a0c 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -965,7 +965,8 @@ class TestSalesInvoice(unittest.TestCase):
pos_return.insert()
pos_return.submit()
- self.assertEqual(pos_return.get("payments")[0].amount, -1000)
+ self.assertEqual(pos_return.get("payments")[0].amount, -500)
+ self.assertEqual(pos_return.get("payments")[1].amount, -500)
def test_pos_change_amount(self):
make_pos_profile(
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index a307a6c17cd..7f1a1eccc45 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -247,6 +247,7 @@
},
{
"collapsible": 1,
+ "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"fieldname": "discount_and_margin",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -876,7 +877,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-10-10 20:57:38.340026",
+ "modified": "2022-10-26 11:38:36.119339",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index f4a50a5f915..6d164eef2be 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -128,6 +128,12 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
new_gl_map = []
for d in gl_map:
cost_center = d.get("cost_center")
+
+ # Validate budget against main cost center
+ validate_expense_against_budget(
+ d, expense_amount=flt(d.debit, precision) - flt(d.credit, precision)
+ )
+
if cost_center and cost_center_allocation.get(cost_center):
for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
gle = copy.deepcopy(d)
diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js
index 7cf14e67385..e1a30a4b77e 100644
--- a/erpnext/accounts/report/accounts_payable/accounts_payable.js
+++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js
@@ -51,6 +51,8 @@ frappe.query_reports["Accounts Payable"] = {
} else {
frappe.query_report.set_filter_value('tax_id', "");
}
+
+ frappe.query_report.refresh();
}
},
{
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 8557c03bd5e..f2ee1eb10ea 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -748,7 +748,7 @@ class ReceivablePayableReport(object):
self.add_accounting_dimensions_filters()
- def get_cost_center_conditions(self, conditions):
+ def get_cost_center_conditions(self):
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
cost_center_list = [
center.name
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html
index 378fa3791c1..c04f518d7e6 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.html
+++ b/erpnext/accounts/report/general_ledger/general_ledger.html
@@ -52,22 +52,22 @@
{% } %}
- {%= format_currency(data[i].debit, filters.presentation_currency) %} |
+ {%= format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %}
- {%= format_currency(data[i].credit, filters.presentation_currency) %} |
+ {%= format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %}
{% } else { %}
|
|
{%= frappe.format(data[i].account, {fieldtype: "Link"}) || " " %} |
- {%= data[i].account && format_currency(data[i].debit, filters.presentation_currency) %}
+ {%= data[i].account && format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %}
|
- {%= data[i].account && format_currency(data[i].credit, filters.presentation_currency) %}
+ {%= data[i].account && format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %}
|
{% } %}
- {%= format_currency(data[i].balance, filters.presentation_currency) %}
+ {%= format_currency(data[i].balance, filters.presentation_currency || data[i].account_currency) %}
|
{% } %}
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 370b13bb98d..5c1311d68a9 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -221,7 +221,7 @@ class TestAsset(AssetSetup):
asset.precision("gross_purchase_amount"),
)
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
- asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
+ asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
@@ -283,7 +283,7 @@ class TestAsset(AssetSetup):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
- asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
+ asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index 8758e9c17db..d5913c59463 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -135,6 +135,7 @@ class AssetRepair(AccountsController):
"basic_rate": stock_item.valuation_rate,
"serial_no": stock_item.serial_no,
"cost_center": self.cost_center,
+ "project": self.project,
},
)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index ddf81ca3aec..06fdea030c8 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -101,6 +101,11 @@ frappe.ui.form.on("Purchase Order", {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
+
+ // On cancel and amending a purchase order with advance payment, reset advance paid amount
+ if (frm.is_new()) {
+ frm.set_value("advance_paid", 0)
+ }
},
apply_tds: function(frm) {
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 82e92e87bc9..b8203bd1286 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -777,6 +777,7 @@
},
{
"collapsible": 1,
+ "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"fieldname": "discount_and_margin_section",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -894,7 +895,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-09-07 11:12:38.634976",
+ "modified": "2022-10-26 16:47:41.364387",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index cbcccce5f71..b5836c9070a 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -889,24 +889,33 @@ class calculate_taxes_and_totals(object):
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
def set_total_amount_to_default_mop(self, total_amount_to_pay):
- default_mode_of_payment = frappe.db.get_value(
- "POS Payment Method",
- {"parent": self.doc.pos_profile, "default": 1},
- ["mode_of_payment"],
- as_dict=1,
- )
-
- if default_mode_of_payment:
- self.doc.payments = []
- self.doc.append(
- "payments",
- {
- "mode_of_payment": default_mode_of_payment.mode_of_payment,
- "amount": total_amount_to_pay,
- "default": 1,
- },
+ total_paid_amount = 0
+ for payment in self.doc.get("payments"):
+ total_paid_amount += (
+ payment.amount if self.doc.party_account_currency == self.doc.currency else payment.base_amount
)
+ pending_amount = total_amount_to_pay - total_paid_amount
+
+ if pending_amount > 0:
+ default_mode_of_payment = frappe.db.get_value(
+ "POS Payment Method",
+ {"parent": self.doc.pos_profile, "default": 1},
+ ["mode_of_payment"],
+ as_dict=1,
+ )
+
+ if default_mode_of_payment:
+ self.doc.payments = []
+ self.doc.append(
+ "payments",
+ {
+ "mode_of_payment": default_mode_of_payment.mode_of_payment,
+ "amount": pending_amount,
+ "default": 1,
+ },
+ )
+
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 7fecb18fad4..dd957c72ac6 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1404,7 +1404,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if (!r.exc && r.message) {
me._set_values_for_item_list(r.message);
if(item) me.set_gross_profit(item);
- if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
+ if (me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
}
}
});
@@ -1577,6 +1577,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
for (let key in pr_row) {
row_to_modify[key] = pr_row[key];
}
+ this.frm.script_manager.copy_from_first_row("items", row_to_modify, ["expense_account", "income_account"]);
});
// free_item_data is a temporary variable
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 386c12b6386..fb64772479b 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -124,6 +124,11 @@ frappe.ui.form.on("Sales Order", {
return query;
});
+ // On cancel and amending a sales order with advance payment, reset advance paid amount
+ if (frm.is_new()) {
+ frm.set_value("advance_paid", 0)
+ }
+
frm.ignore_doctypes_on_cancel_all = ['Purchase Order'];
},
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index f0e9e4b7d92..1f3419fd5dd 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -627,6 +627,7 @@ def make_project(source_name, target_doc=None):
"field_map": {
"name": "sales_order",
"base_grand_total": "estimated_costing",
+ "net_total": "total_sales_amount",
},
},
},
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 2cf836f9fcc..ea0b25f41cc 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -272,6 +272,7 @@
},
{
"collapsible": 1,
+ "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"fieldname": "discount_and_margin",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -842,7 +843,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-09-06 13:24:18.065312",
+ "modified": "2022-10-26 16:05:02.712705",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
index c068ae3b5a4..991ac719cdc 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
@@ -74,7 +74,35 @@ function get_filters() {
]
}
}
- }
+ },
+ {
+ "fieldname":"from_due_date",
+ "label": __("From Due Date"),
+ "fieldtype": "Date",
+ },
+ {
+ "fieldname":"to_due_date",
+ "label": __("To Due Date"),
+ "fieldtype": "Date",
+ },
+ {
+ "fieldname":"status",
+ "label": __("Status"),
+ "fieldtype": "MultiSelectList",
+ "width": 100,
+ get_data: function(txt) {
+ let status = ["Overdue", "Unpaid", "Completed", "Partly Paid"]
+ let options = []
+ for (let option of status){
+ options.push({
+ "value": option,
+ "label": __(option),
+ "description": ""
+ })
+ }
+ return options
+ }
+ },
]
return filters;
}
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
index 91f4a5e50a5..8bf56865a7d 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
@@ -162,6 +162,12 @@ def build_filter_criterions(filters):
if filters.item:
qb_criterions.append(qb.DocType("Sales Order Item").item_code == filters.item)
+ if filters.from_due_date:
+ qb_criterions.append(qb.DocType("Payment Schedule").due_date.gte(filters.from_due_date))
+
+ if filters.to_due_date:
+ qb_criterions.append(qb.DocType("Payment Schedule").due_date.lte(filters.to_due_date))
+
return qb_criterions
@@ -279,11 +285,19 @@ def prepare_chart(s_orders):
return chart
+def filter_on_calculated_status(filters, sales_orders):
+ if filters.status and sales_orders:
+ return [x for x in sales_orders if x.status in filters.status]
+ return sales_orders
+
+
def execute(filters=None):
columns = get_columns()
sales_orders, so_invoices = get_so_with_invoices(filters)
sales_orders, so_invoices = set_payment_terms_statuses(sales_orders, so_invoices, filters)
+ sales_orders = filter_on_calculated_status(filters, sales_orders)
+
prepare_chart(sales_orders)
data = sales_orders
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
index 9d542f5079c..525ae8e7ea7 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
@@ -2,7 +2,7 @@ import datetime
import frappe
from frappe.tests.utils import FrappeTestCase
-from frappe.utils import add_days
+from frappe.utils import add_days, nowdate
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
@@ -77,12 +77,14 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
sinv.insert()
sinv.submit()
columns, data, message, chart = execute(
- {
- "company": "_Test Company",
- "period_start_date": "2021-06-01",
- "period_end_date": "2021-06-30",
- "item": item.item_code,
- }
+ frappe._dict(
+ {
+ "company": "_Test Company",
+ "period_start_date": "2021-06-01",
+ "period_end_date": "2021-06-30",
+ "item": item.item_code,
+ }
+ )
)
expected_value = [
@@ -167,12 +169,14 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
sinv.insert()
sinv.submit()
columns, data, message, chart = execute(
- {
- "company": "_Test Company",
- "period_start_date": "2021-06-01",
- "period_end_date": "2021-06-30",
- "item": item.item_code,
- }
+ frappe._dict(
+ {
+ "company": "_Test Company",
+ "period_start_date": "2021-06-01",
+ "period_end_date": "2021-06-30",
+ "item": item.item_code,
+ }
+ )
)
# report defaults to company currency.
@@ -338,3 +342,60 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
with self.subTest(filters=filters):
columns, data, message, chart = execute(filters)
self.assertEqual(data, expected_values_for_group_filters[idx])
+
+ def test_04_due_date_filter(self):
+ self.create_payment_terms_template()
+ item = create_item(item_code="_Test Excavator 1", is_stock_item=0)
+ transaction_date = nowdate()
+ so = make_sales_order(
+ transaction_date=add_days(transaction_date, -30),
+ delivery_date=add_days(transaction_date, -15),
+ item=item.item_code,
+ qty=10,
+ rate=100000,
+ do_not_save=True,
+ )
+ so.po_no = ""
+ so.taxes_and_charges = ""
+ so.taxes = ""
+ so.payment_terms_template = self.template.name
+ so.save()
+ so.submit()
+
+ # make invoice with 60% of the total sales order value
+ sinv = make_sales_invoice(so.name)
+ sinv.taxes_and_charges = ""
+ sinv.taxes = ""
+ sinv.items[0].qty = 6
+ sinv.insert()
+ sinv.submit()
+ columns, data, message, chart = execute(
+ frappe._dict(
+ {
+ "company": "_Test Company",
+ "item": item.item_code,
+ "from_due_date": add_days(transaction_date, -30),
+ "to_due_date": add_days(transaction_date, -15),
+ }
+ )
+ )
+
+ expected_value = [
+ {
+ "name": so.name,
+ "customer": so.customer,
+ "submitted": datetime.date.fromisoformat(add_days(transaction_date, -30)),
+ "status": "Completed",
+ "payment_term": None,
+ "description": "_Test 50-50",
+ "due_date": datetime.date.fromisoformat(add_days(transaction_date, -15)),
+ "invoice_portion": 50.0,
+ "currency": "INR",
+ "base_payment_amount": 500000.0,
+ "paid_amount": 500000.0,
+ "invoices": "," + sinv.name,
+ },
+ ]
+ # Only the first term should be pulled
+ self.assertEqual(len(data), 1)
+ self.assertEqual(data, expected_value)
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 0a5cbabab0b..77c32534321 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -261,6 +261,7 @@
},
{
"collapsible": 1,
+ "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"fieldname": "discount_and_margin",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -814,7 +815,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-10-12 03:36:05.344847",
+ "modified": "2022-10-26 16:05:17.720768",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
index 9e8c10b394d..7b99b0097bb 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
@@ -121,18 +121,24 @@ class InventoryDimension(Document):
if self.apply_to_all_doctypes:
for doctype in get_inventory_documents():
- custom_fields.setdefault(doctype[0], dimension_fields)
- else:
+ if not field_exists(doctype[0], self.source_fieldname):
+ custom_fields.setdefault(doctype[0], dimension_fields)
+ elif not field_exists(self.document_type, self.source_fieldname):
custom_fields.setdefault(self.document_type, dimension_fields)
if not frappe.db.get_value(
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
- ):
+ ) and not field_exists("Stock Ledger Entry", self.target_fieldname):
dimension_field = dimension_fields[1]
dimension_field["fieldname"] = self.target_fieldname
custom_fields["Stock Ledger Entry"] = dimension_field
- create_custom_fields(custom_fields)
+ if custom_fields:
+ create_custom_fields(custom_fields)
+
+
+def field_exists(doctype, fieldname) -> str or None:
+ return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name")
@frappe.whitelist()
diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
index 19ddc449f0e..52b3deb3f01 100644
--- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
@@ -191,6 +191,21 @@ class TestInventoryDimension(FrappeTestCase):
self.assertEqual(sle_rack, "Rack 1")
+ def test_check_standard_dimensions(self):
+ create_inventory_dimension(
+ reference_document="Project",
+ type_of_transaction="Outward",
+ dimension_name="Project",
+ apply_to_all_doctypes=0,
+ document_type="Stock Ledger Entry",
+ )
+
+ self.assertFalse(
+ frappe.db.get_value(
+ "Custom Field", {"fieldname": "project", "dt": "Stock Ledger Entry"}, "name"
+ )
+ )
+
def prepare_test_data():
if not frappe.db.exists("DocType", "Shelf"):
diff --git a/erpnext/stock/doctype/material_request/material_request_dashboard.py b/erpnext/stock/doctype/material_request/material_request_dashboard.py
index b073e6a22ee..691a8b39b1b 100644
--- a/erpnext/stock/doctype/material_request/material_request_dashboard.py
+++ b/erpnext/stock/doctype/material_request/material_request_dashboard.py
@@ -4,10 +4,13 @@ from frappe import _
def get_data():
return {
"fieldname": "material_request",
+ "internal_links": {
+ "Sales Order": ["items", "sales_order"],
+ },
"transactions": [
{
"label": _("Reference"),
- "items": ["Request for Quotation", "Supplier Quotation", "Purchase Order"],
+ "items": ["Sales Order", "Request for Quotation", "Supplier Quotation", "Purchase Order"],
},
{"label": _("Stock"), "items": ["Stock Entry", "Purchase Receipt", "Pick List"]},
{"label": _("Manufacturing"), "items": ["Work Order"]},
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
index 06ba9365561..b3ae7b58b49 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
@@ -12,13 +12,17 @@ def get_data():
"Purchase Receipt": "return_against",
},
"internal_links": {
+ "Material Request": ["items", "material_request"],
"Purchase Order": ["items", "purchase_order"],
"Project": ["items", "project"],
"Quality Inspection": ["items", "quality_inspection"],
},
"transactions": [
{"label": _("Related"), "items": ["Purchase Invoice", "Landed Cost Voucher", "Asset"]},
- {"label": _("Reference"), "items": ["Purchase Order", "Quality Inspection", "Project"]},
+ {
+ "label": _("Reference"),
+ "items": ["Material Request", "Purchase Order", "Quality Inspection", "Project"],
+ },
{"label": _("Returns"), "items": ["Purchase Receipt"]},
{"label": _("Subscription"), "items": ["Auto Repeat"]},
],
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 772736e0adc..474ee92e268 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -919,6 +919,7 @@
},
{
"collapsible": 1,
+ "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
"fieldname": "discount_and_margin_section",
"fieldtype": "Section Break",
"label": "Discount and Margin"
@@ -1000,7 +1001,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-10-12 03:37:59.516609",
+ "modified": "2022-10-26 16:06:02.524435",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
index f308e9e41f1..a6fc049cbde 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
@@ -3,6 +3,7 @@
import frappe
from frappe import _
+from frappe.query_builder.functions import Abs, Sum
from frappe.utils import flt, getdate
@@ -11,8 +12,6 @@ def execute(filters=None):
filters = {}
float_precision = frappe.db.get_default("float_precision")
- condition = get_condition(filters)
-
avg_daily_outgoing = 0
diff = ((getdate(filters.get("to_date")) - getdate(filters.get("from_date"))).days) + 1
if diff <= 0:
@@ -20,8 +19,8 @@ def execute(filters=None):
columns = get_columns()
items = get_item_info(filters)
- consumed_item_map = get_consumed_items(condition)
- delivered_item_map = get_delivered_items(condition)
+ consumed_item_map = get_consumed_items(filters)
+ delivered_item_map = get_delivered_items(filters)
data = []
for item in items:
@@ -71,76 +70,86 @@ def get_columns():
def get_item_info(filters):
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
- conditions = [get_item_group_condition(filters.get("item_group"))]
- if filters.get("brand"):
- conditions.append("item.brand=%(brand)s")
- conditions.append("is_stock_item = 1")
-
- return frappe.db.sql(
- """select name, item_name, description, brand, item_group,
- safety_stock, lead_time_days from `tabItem` item where {}""".format(
- " and ".join(conditions)
- ),
- filters,
- as_dict=1,
+ item = frappe.qb.DocType("Item")
+ query = (
+ frappe.qb.from_(item)
+ .select(
+ item.name,
+ item.item_name,
+ item.description,
+ item.brand,
+ item.item_group,
+ item.safety_stock,
+ item.lead_time_days,
+ )
+ .where(item.is_stock_item == 1)
)
+ if brand := filters.get("brand"):
+ query = query.where(item.brand == brand)
-def get_consumed_items(condition):
+ if conditions := get_item_group_condition(filters.get("item_group"), item):
+ query = query.where(conditions)
+
+ return query.run(as_dict=True)
+
+
+def get_consumed_items(filters):
purpose_to_exclude = [
"Material Transfer for Manufacture",
"Material Transfer",
"Send to Subcontractor",
]
- condition += """
- and (
- purpose is NULL
- or purpose not in ({})
+ se = frappe.qb.DocType("Stock Entry")
+ sle = frappe.qb.DocType("Stock Ledger Entry")
+ query = (
+ frappe.qb.from_(sle)
+ .left_join(se)
+ .on(sle.voucher_no == se.name)
+ .select(sle.item_code, Abs(Sum(sle.actual_qty)).as_("consumed_qty"))
+ .where(
+ (sle.actual_qty < 0)
+ & (sle.is_cancelled == 0)
+ & (sle.voucher_type.notin(["Delivery Note", "Sales Invoice"]))
+ & ((se.purpose.isnull()) | (se.purpose.notin(purpose_to_exclude)))
)
- """.format(
- ", ".join(f"'{p}'" for p in purpose_to_exclude)
+ .groupby(sle.item_code)
)
- condition = condition.replace("posting_date", "sle.posting_date")
+ query = get_filtered_query(filters, sle, query)
- consumed_items = frappe.db.sql(
- """
- select item_code, abs(sum(actual_qty)) as consumed_qty
- from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se
- on sle.voucher_no = se.name
- where
- actual_qty < 0
- and is_cancelled = 0
- and voucher_type not in ('Delivery Note', 'Sales Invoice')
- %s
- group by item_code"""
- % condition,
- as_dict=1,
- )
+ consumed_items = query.run(as_dict=True)
consumed_items_map = {item.item_code: item.consumed_qty for item in consumed_items}
return consumed_items_map
-def get_delivered_items(condition):
- dn_items = frappe.db.sql(
- """select dn_item.item_code, sum(dn_item.stock_qty) as dn_qty
- from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
- where dn.name = dn_item.parent and dn.docstatus = 1 %s
- group by dn_item.item_code"""
- % (condition),
- as_dict=1,
+def get_delivered_items(filters):
+ parent = frappe.qb.DocType("Delivery Note")
+ child = frappe.qb.DocType("Delivery Note Item")
+ query = (
+ frappe.qb.from_(parent)
+ .from_(child)
+ .select(child.item_code, Sum(child.stock_qty).as_("dn_qty"))
+ .where((parent.name == child.parent) & (parent.docstatus == 1))
+ .groupby(child.item_code)
)
+ query = get_filtered_query(filters, parent, query)
- si_items = frappe.db.sql(
- """select si_item.item_code, sum(si_item.stock_qty) as si_qty
- from `tabSales Invoice` si, `tabSales Invoice Item` si_item
- where si.name = si_item.parent and si.docstatus = 1 and
- si.update_stock = 1 %s
- group by si_item.item_code"""
- % (condition),
- as_dict=1,
+ dn_items = query.run(as_dict=True)
+
+ parent = frappe.qb.DocType("Sales Invoice")
+ child = frappe.qb.DocType("Sales Invoice Item")
+ query = (
+ frappe.qb.from_(parent)
+ .from_(child)
+ .select(child.item_code, Sum(child.stock_qty).as_("si_qty"))
+ .where((parent.name == child.parent) & (parent.docstatus == 1) & (parent.update_stock == 1))
+ .groupby(child.item_code)
)
+ query = get_filtered_query(filters, parent, query)
+
+ si_items = query.run(as_dict=True)
dn_item_map = {}
for item in dn_items:
@@ -152,13 +161,10 @@ def get_delivered_items(condition):
return dn_item_map
-def get_condition(filters):
- conditions = ""
+def get_filtered_query(filters, table, query):
if filters.get("from_date") and filters.get("to_date"):
- conditions += " and posting_date between '%s' and '%s'" % (
- filters["from_date"],
- filters["to_date"],
- )
+ query = query.where(table.posting_date.between(filters["from_date"], filters["to_date"]))
else:
- frappe.throw(_("From and To dates required"))
- return conditions
+ frappe.throw(_("From and To dates are required"))
+
+ return query
diff --git a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
index 854875a0532..9e75201bd14 100644
--- a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
+++ b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
@@ -4,7 +4,9 @@
import frappe
from frappe import _
+from frappe.query_builder.functions import IfNull
from frappe.utils import flt
+from pypika.terms import ExistsCriterion
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
@@ -123,43 +125,65 @@ def get_items(filters):
pb_details = frappe._dict()
item_details = frappe._dict()
- conditions = get_parent_item_conditions(filters)
- parent_item_details = frappe.db.sql(
- """
- select item.name as item_code, item.item_name, pb.description, item.item_group, item.brand, item.stock_uom
- from `tabItem` item
- inner join `tabProduct Bundle` pb on pb.new_item_code = item.name
- where ifnull(item.disabled, 0) = 0 {0}
- """.format(
- conditions
- ),
- filters,
- as_dict=1,
- ) # nosec
+ item = frappe.qb.DocType("Item")
+ pb = frappe.qb.DocType("Product Bundle")
+
+ query = (
+ frappe.qb.from_(item)
+ .inner_join(pb)
+ .on(pb.new_item_code == item.name)
+ .select(
+ item.name.as_("item_code"),
+ item.item_name,
+ pb.description,
+ item.item_group,
+ item.brand,
+ item.stock_uom,
+ )
+ .where(IfNull(item.disabled, 0) == 0)
+ )
+
+ if item_code := filters.get("item_code"):
+ query = query.where(item.item_code == item_code)
+ else:
+ if brand := filters.get("brand"):
+ query = query.where(item.brand == brand)
+ if item_group := filters.get("item_group"):
+ if conditions := get_item_group_condition(item_group, item):
+ query = query.where(conditions)
+
+ parent_item_details = query.run(as_dict=True)
parent_items = []
for d in parent_item_details:
parent_items.append(d.item_code)
item_details[d.item_code] = d
+ child_item_details = []
if parent_items:
- child_item_details = frappe.db.sql(
- """
- select
- pb.new_item_code as parent_item, pbi.item_code, item.item_name, pbi.description, item.item_group, item.brand,
- item.stock_uom, pbi.uom, pbi.qty
- from `tabProduct Bundle Item` pbi
- inner join `tabProduct Bundle` pb on pb.name = pbi.parent
- inner join `tabItem` item on item.name = pbi.item_code
- where pb.new_item_code in ({0})
- """.format(
- ", ".join(["%s"] * len(parent_items))
- ),
- parent_items,
- as_dict=1,
- ) # nosec
- else:
- child_item_details = []
+ item = frappe.qb.DocType("Item")
+ pb = frappe.qb.DocType("Product Bundle")
+ pbi = frappe.qb.DocType("Product Bundle Item")
+
+ child_item_details = (
+ frappe.qb.from_(pbi)
+ .inner_join(pb)
+ .on(pb.name == pbi.parent)
+ .inner_join(item)
+ .on(item.name == pbi.item_code)
+ .select(
+ pb.new_item_code.as_("parent_item"),
+ pbi.item_code,
+ item.item_name,
+ pbi.description,
+ item.item_group,
+ item.brand,
+ item.stock_uom,
+ pbi.uom,
+ pbi.qty,
+ )
+ .where(pb.new_item_code.isin(parent_items))
+ ).run(as_dict=1)
child_items = set()
for d in child_item_details:
@@ -184,58 +208,42 @@ def get_stock_ledger_entries(filters, items):
if not items:
return []
- item_conditions_sql = " and sle.item_code in ({})".format(
- ", ".join(frappe.db.escape(i) for i in items)
+ sle = frappe.qb.DocType("Stock Ledger Entry")
+ sle2 = frappe.qb.DocType("Stock Ledger Entry")
+
+ query = (
+ frappe.qb.from_(sle)
+ .force_index("posting_sort_index")
+ .left_join(sle2)
+ .on(
+ (sle.item_code == sle2.item_code)
+ & (sle.warehouse == sle2.warehouse)
+ & (sle.posting_date < sle2.posting_date)
+ & (sle.posting_time < sle2.posting_time)
+ & (sle.name < sle2.name)
+ )
+ .select(sle.item_code, sle.warehouse, sle.qty_after_transaction, sle.company)
+ .where((sle2.name.isnull()) & (sle.docstatus < 2) & (sle.item_code.isin(items)))
)
- conditions = get_sle_conditions(filters)
-
- return frappe.db.sql(
- """
- select
- sle.item_code, sle.warehouse, sle.qty_after_transaction, sle.company
- from
- `tabStock Ledger Entry` sle force index (posting_sort_index)
- left join `tabStock Ledger Entry` sle2 on
- sle.item_code = sle2.item_code and sle.warehouse = sle2.warehouse
- and (sle.posting_date, sle.posting_time, sle.name) < (sle2.posting_date, sle2.posting_time, sle2.name)
- where sle2.name is null and sle.docstatus < 2 %s %s"""
- % (item_conditions_sql, conditions),
- as_dict=1,
- ) # nosec
-
-
-def get_parent_item_conditions(filters):
- conditions = []
-
- if filters.get("item_code"):
- conditions.append("item.item_code = %(item_code)s")
+ if date := filters.get("date"):
+ query = query.where(sle.posting_date <= date)
else:
- if filters.get("brand"):
- conditions.append("item.brand=%(brand)s")
- if filters.get("item_group"):
- conditions.append(get_item_group_condition(filters.get("item_group")))
-
- conditions = " and ".join(conditions)
- return "and {0}".format(conditions) if conditions else ""
-
-
-def get_sle_conditions(filters):
- conditions = ""
- if not filters.get("date"):
frappe.throw(_("'Date' is required"))
- conditions += " and sle.posting_date <= %s" % frappe.db.escape(filters.get("date"))
-
if filters.get("warehouse"):
warehouse_details = frappe.db.get_value(
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
)
- if warehouse_details:
- conditions += (
- " and exists (select name from `tabWarehouse` wh \
- where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"
- % (warehouse_details.lft, warehouse_details.rgt)
- ) # nosec
- return conditions
+ if warehouse_details:
+ wh = frappe.qb.DocType("Warehouse")
+ query = query.where(
+ ExistsCriterion(
+ frappe.qb.from_(wh)
+ .select(wh.name)
+ .where((wh.lft >= warehouse_details.lft) & (wh.rgt <= warehouse_details.rgt))
+ )
+ )
+
+ return query.run(as_dict=True)
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index a95119736a0..af7f20f3ccf 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -305,20 +305,25 @@ def get_inventory_dimension_fields():
def get_items(filters):
+ item = frappe.qb.DocType("Item")
+ query = frappe.qb.from_(item).select(item.name)
conditions = []
- if filters.get("item_code"):
- conditions.append("item.name=%(item_code)s")
+
+ if item_code := filters.get("item_code"):
+ conditions.append(item.name == item_code)
else:
- if filters.get("brand"):
- conditions.append("item.brand=%(brand)s")
- if filters.get("item_group"):
- conditions.append(get_item_group_condition(filters.get("item_group")))
+ if brand := filters.get("brand"):
+ conditions.append(item.brand == brand)
+ if item_group := filters.get("item_group"):
+ if condition := get_item_group_condition(item_group, item):
+ conditions.append(condition)
items = []
if conditions:
- items = frappe.db.sql_list(
- """select name from `tabItem` item where {}""".format(" and ".join(conditions)), filters
- )
+ for condition in conditions:
+ query = query.where(condition)
+ items = [r[0] for r in query.run()]
+
return items
@@ -330,29 +335,22 @@ def get_item_details(items, sl_entries, include_uom):
if not items:
return item_details
- cf_field = cf_join = ""
+ item = frappe.qb.DocType("Item")
+ query = (
+ frappe.qb.from_(item)
+ .select(item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom)
+ .where(item.name.isin(items))
+ )
+
if include_uom:
- cf_field = ", ucd.conversion_factor"
- cf_join = (
- "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s"
- % frappe.db.escape(include_uom)
+ ucd = frappe.qb.DocType("UOM Conversion Detail")
+ query = (
+ query.left_join(ucd)
+ .on((ucd.parent == item.name) & (ucd.uom == include_uom))
+ .select(ucd.conversion_factor)
)
- res = frappe.db.sql(
- """
- select
- item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom {cf_field}
- from
- `tabItem` item
- {cf_join}
- where
- item.name in ({item_codes})
- """.format(
- cf_field=cf_field, cf_join=cf_join, item_codes=",".join(["%s"] * len(items))
- ),
- items,
- as_dict=1,
- )
+ res = query.run(as_dict=True)
for item in res:
item_details.setdefault(item.name, item)
@@ -427,16 +425,28 @@ def get_warehouse_condition(warehouse):
return ""
-def get_item_group_condition(item_group):
+def get_item_group_condition(item_group, item_table=None):
item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1)
if item_group_details:
- return (
- "item.item_group in (select ig.name from `tabItem Group` ig \
- where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"
- % (item_group_details.lft, item_group_details.rgt)
- )
-
- return ""
+ if item_table:
+ ig = frappe.qb.DocType("Item Group")
+ return item_table.item_group.isin(
+ (
+ frappe.qb.from_(ig)
+ .select(ig.name)
+ .where(
+ (ig.lft >= item_group_details.lft)
+ & (ig.rgt <= item_group_details.rgt)
+ & (item_table.item_group == ig.name)
+ )
+ )
+ )
+ else:
+ return (
+ "item.item_group in (select ig.name from `tabItem Group` ig \
+ where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"
+ % (item_group_details.lft, item_group_details.rgt)
+ )
def check_inventory_dimension_filters_applied(filters) -> bool: