Merge branch 'develop' of https://github.com/frappe/erpnext into sales_invoice_default_mop
This commit is contained in:
commit
a4f7079270
@ -107,7 +107,7 @@ class Budget(Document):
|
|||||||
self.naming_series = f"{{{frappe.scrub(self.budget_against)}}}./.{self.fiscal_year}/.###"
|
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)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
if args.get("company") and not args.fiscal_year:
|
if args.get("company") and not args.fiscal_year:
|
||||||
@ -175,13 +175,13 @@ def validate_expense_against_budget(args):
|
|||||||
) # nosec
|
) # nosec
|
||||||
|
|
||||||
if budget_records:
|
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:
|
for budget in budget_records:
|
||||||
if flt(budget.budget_amount):
|
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)
|
yearly_action, monthly_action = get_actions(args, budget)
|
||||||
|
|
||||||
if monthly_action in ["Stop", "Warn"]:
|
if monthly_action in ["Stop", "Warn"]:
|
||||||
|
@ -334,6 +334,39 @@ class TestBudget(unittest.TestCase):
|
|||||||
budget.cancel()
|
budget.cancel()
|
||||||
jv.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):
|
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
|
||||||
if budget_against_field == "project":
|
if budget_against_field == "project":
|
||||||
|
@ -312,8 +312,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_outstanding(doctype, docname, company, child, due_date) {
|
get_outstanding(doctype, docname, company, child) {
|
||||||
var me = this;
|
|
||||||
var args = {
|
var args = {
|
||||||
"doctype": doctype,
|
"doctype": doctype,
|
||||||
"docname": docname,
|
"docname": docname,
|
||||||
|
@ -1210,6 +1210,7 @@ def get_outstanding(args):
|
|||||||
args = json.loads(args)
|
args = json.loads(args)
|
||||||
|
|
||||||
company_currency = erpnext.get_company_currency(args.get("company"))
|
company_currency = erpnext.get_company_currency(args.get("company"))
|
||||||
|
due_date = None
|
||||||
|
|
||||||
if args.get("doctype") == "Journal Entry":
|
if args.get("doctype") == "Journal Entry":
|
||||||
condition = " and party=%(party)s" if args.get("party") else ""
|
condition = " and party=%(party)s" if args.get("party") else ""
|
||||||
@ -1234,10 +1235,12 @@ def get_outstanding(args):
|
|||||||
invoice = frappe.db.get_value(
|
invoice = frappe.db.get_value(
|
||||||
args["doctype"],
|
args["doctype"],
|
||||||
args["docname"],
|
args["docname"],
|
||||||
["outstanding_amount", "conversion_rate", scrub(party_type)],
|
["outstanding_amount", "conversion_rate", scrub(party_type), "due_date"],
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
due_date = invoice.get("due_date")
|
||||||
|
|
||||||
exchange_rate = (
|
exchange_rate = (
|
||||||
invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
|
invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
|
||||||
)
|
)
|
||||||
@ -1260,6 +1263,7 @@ def get_outstanding(args):
|
|||||||
"exchange_rate": exchange_rate,
|
"exchange_rate": exchange_rate,
|
||||||
"party_type": party_type,
|
"party_type": party_type,
|
||||||
"party": invoice.get(scrub(party_type)),
|
"party": invoice.get(scrub(party_type)),
|
||||||
|
"reference_due_date": due_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@
|
|||||||
{
|
{
|
||||||
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
|
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
|
||||||
"fieldname": "reference_due_date",
|
"fieldname": "reference_due_date",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Date",
|
||||||
"label": "Reference Due Date",
|
"label": "Reference Due Date",
|
||||||
"no_copy": 1
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
@ -284,7 +284,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-10-13 17:07:17.999191",
|
"modified": "2022-10-26 20:03:10.906259",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Journal Entry Account",
|
"name": "Journal Entry Account",
|
||||||
|
@ -52,7 +52,10 @@
|
|||||||
"free_item_rate",
|
"free_item_rate",
|
||||||
"column_break_42",
|
"column_break_42",
|
||||||
"free_item_uom",
|
"free_item_uom",
|
||||||
|
"round_free_qty",
|
||||||
"is_recursive",
|
"is_recursive",
|
||||||
|
"recurse_for",
|
||||||
|
"apply_recursion_over",
|
||||||
"section_break_23",
|
"section_break_23",
|
||||||
"valid_from",
|
"valid_from",
|
||||||
"valid_upto",
|
"valid_upto",
|
||||||
@ -578,12 +581,34 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Naming Series",
|
"label": "Naming Series",
|
||||||
"options": "PRLE-.####"
|
"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",
|
"icon": "fa fa-gift",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-09-16 16:00:38.356266",
|
"modified": "2022-10-13 19:05:35.056304",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Pricing Rule",
|
"name": "Pricing Rule",
|
||||||
|
@ -24,6 +24,7 @@ class PricingRule(Document):
|
|||||||
self.validate_applicable_for_selling_or_buying()
|
self.validate_applicable_for_selling_or_buying()
|
||||||
self.validate_min_max_amt()
|
self.validate_min_max_amt()
|
||||||
self.validate_min_max_qty()
|
self.validate_min_max_qty()
|
||||||
|
self.validate_recursion()
|
||||||
self.cleanup_fields_value()
|
self.cleanup_fields_value()
|
||||||
self.validate_rate_or_discount()
|
self.validate_rate_or_discount()
|
||||||
self.validate_max_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):
|
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"))
|
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):
|
def cleanup_fields_value(self):
|
||||||
for logic_field in ["apply_on", "applicable_for", "rate_or_discount"]:
|
for logic_field in ["apply_on", "applicable_for", "rate_or_discount"]:
|
||||||
fieldname = frappe.scrub(self.get(logic_field) or "")
|
fieldname = frappe.scrub(self.get(logic_field) or "")
|
||||||
|
@ -710,6 +710,132 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
|
|
||||||
item.delete()
|
item.delete()
|
||||||
|
|
||||||
|
def test_item_group_price_with_blank_uom_pricing_rule(self):
|
||||||
|
group = frappe.get_doc(doctype="Item Group", item_group_name="_Test Pricing Rule Item Group")
|
||||||
|
group.save()
|
||||||
|
properties = {
|
||||||
|
"item_code": "Item with Group Blank UOM",
|
||||||
|
"item_group": "_Test Pricing Rule Item Group",
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
"sales_uom": "Box",
|
||||||
|
"uoms": [dict(uom="Box", conversion_factor=10)],
|
||||||
|
}
|
||||||
|
item = make_item(properties=properties)
|
||||||
|
|
||||||
|
make_item_price("Item with Group Blank UOM", "_Test Price List", 100)
|
||||||
|
|
||||||
|
pricing_rule_record = {
|
||||||
|
"doctype": "Pricing Rule",
|
||||||
|
"title": "_Test Item with Group Blank UOM Rule",
|
||||||
|
"apply_on": "Item Group",
|
||||||
|
"item_groups": [
|
||||||
|
{
|
||||||
|
"item_group": "_Test Pricing Rule Item Group",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selling": 1,
|
||||||
|
"currency": "INR",
|
||||||
|
"rate_or_discount": "Rate",
|
||||||
|
"rate": 101,
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
rule = frappe.get_doc(pricing_rule_record)
|
||||||
|
rule.insert()
|
||||||
|
|
||||||
|
si = create_sales_invoice(
|
||||||
|
do_not_save=True, item_code="Item with Group Blank UOM", uom="Box", conversion_factor=10
|
||||||
|
)
|
||||||
|
si.selling_price_list = "_Test Price List"
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
# If UOM is blank consider it as stock UOM and apply pricing_rule on all UOM.
|
||||||
|
# rate is 101, Selling UOM is Box that have conversion_factor of 10 so 101 * 10 = 1010
|
||||||
|
self.assertEqual(si.items[0].price_list_rate, 1010)
|
||||||
|
self.assertEqual(si.items[0].rate, 1010)
|
||||||
|
|
||||||
|
si.delete()
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_save=True, item_code="Item with Group Blank UOM", uom="Nos")
|
||||||
|
si.selling_price_list = "_Test Price List"
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
# UOM is blank so consider it as stock UOM and apply pricing_rule on all UOM.
|
||||||
|
# rate is 101, Selling UOM is Nos that have conversion_factor of 1 so 101 * 1 = 101
|
||||||
|
self.assertEqual(si.items[0].price_list_rate, 101)
|
||||||
|
self.assertEqual(si.items[0].rate, 101)
|
||||||
|
|
||||||
|
si.delete()
|
||||||
|
rule.delete()
|
||||||
|
frappe.get_doc("Item Price", {"item_code": "Item with Group Blank UOM"}).delete()
|
||||||
|
item.delete()
|
||||||
|
group.delete()
|
||||||
|
|
||||||
|
def test_item_group_price_with_selling_uom_pricing_rule(self):
|
||||||
|
group = frappe.get_doc(doctype="Item Group", item_group_name="_Test Pricing Rule Item Group UOM")
|
||||||
|
group.save()
|
||||||
|
properties = {
|
||||||
|
"item_code": "Item with Group UOM other than Stock",
|
||||||
|
"item_group": "_Test Pricing Rule Item Group UOM",
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
"sales_uom": "Box",
|
||||||
|
"uoms": [dict(uom="Box", conversion_factor=10)],
|
||||||
|
}
|
||||||
|
item = make_item(properties=properties)
|
||||||
|
|
||||||
|
make_item_price("Item with Group UOM other than Stock", "_Test Price List", 100)
|
||||||
|
|
||||||
|
pricing_rule_record = {
|
||||||
|
"doctype": "Pricing Rule",
|
||||||
|
"title": "_Test Item with Group UOM other than Stock Rule",
|
||||||
|
"apply_on": "Item Group",
|
||||||
|
"item_groups": [
|
||||||
|
{
|
||||||
|
"item_group": "_Test Pricing Rule Item Group UOM",
|
||||||
|
"uom": "Box",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selling": 1,
|
||||||
|
"currency": "INR",
|
||||||
|
"rate_or_discount": "Rate",
|
||||||
|
"rate": 101,
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
rule = frappe.get_doc(pricing_rule_record)
|
||||||
|
rule.insert()
|
||||||
|
|
||||||
|
si = create_sales_invoice(
|
||||||
|
do_not_save=True,
|
||||||
|
item_code="Item with Group UOM other than Stock",
|
||||||
|
uom="Box",
|
||||||
|
conversion_factor=10,
|
||||||
|
)
|
||||||
|
si.selling_price_list = "_Test Price List"
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
# UOM is Box so apply pricing_rule only on Box UOM.
|
||||||
|
# Selling UOM is Box and as both UOM are same no need to multiply by conversion_factor.
|
||||||
|
self.assertEqual(si.items[0].price_list_rate, 101)
|
||||||
|
self.assertEqual(si.items[0].rate, 101)
|
||||||
|
|
||||||
|
si.delete()
|
||||||
|
|
||||||
|
si = create_sales_invoice(
|
||||||
|
do_not_save=True, item_code="Item with Group UOM other than Stock", uom="Nos"
|
||||||
|
)
|
||||||
|
si.selling_price_list = "_Test Price List"
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
# UOM is Box so pricing_rule won't apply as selling_uom is Nos.
|
||||||
|
# As Pricing Rule is not applied price of 100 will be fetched from Item Price List.
|
||||||
|
self.assertEqual(si.items[0].price_list_rate, 100)
|
||||||
|
self.assertEqual(si.items[0].rate, 100)
|
||||||
|
|
||||||
|
si.delete()
|
||||||
|
rule.delete()
|
||||||
|
frappe.get_doc("Item Price", {"item_code": "Item with Group UOM other than Stock"}).delete()
|
||||||
|
item.delete()
|
||||||
|
group.delete()
|
||||||
|
|
||||||
def test_pricing_rule_for_different_currency(self):
|
def test_pricing_rule_for_different_currency(self):
|
||||||
make_item("Test Sanitizer Item")
|
make_item("Test Sanitizer Item")
|
||||||
|
|
||||||
@ -943,6 +1069,45 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
si.delete()
|
si.delete()
|
||||||
rule.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"]
|
test_dependencies = ["Campaign"]
|
||||||
|
|
||||||
|
@ -127,6 +127,12 @@ def _get_pricing_rules(apply_on, args, values):
|
|||||||
values["variant_of"] = args.variant_of
|
values["variant_of"] = args.variant_of
|
||||||
elif apply_on_field == "item_group":
|
elif apply_on_field == "item_group":
|
||||||
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
|
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
|
||||||
|
if args.get("uom", None):
|
||||||
|
item_conditions += (
|
||||||
|
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
|
||||||
|
child_doc=child_doc, item_uom=args.get("uom")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
conditions += get_other_conditions(conditions, values, args)
|
conditions += get_other_conditions(conditions, values, args)
|
||||||
warehouse_conditions = _get_tree_conditions(args, "Warehouse", "`tabPricing Rule`")
|
warehouse_conditions = _get_tree_conditions(args, "Warehouse", "`tabPricing Rule`")
|
||||||
@ -627,9 +633,13 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
|||||||
|
|
||||||
qty = pricing_rule.free_qty or 1
|
qty = pricing_rule.free_qty or 1
|
||||||
if pricing_rule.is_recursive:
|
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:
|
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 = {
|
free_item_data_args = {
|
||||||
"item_code": free_item,
|
"item_code": free_item,
|
||||||
|
@ -9,6 +9,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
refresh: function(frm){
|
refresh: function(frm){
|
||||||
if(!frm.doc.__islocal) {
|
if(!frm.doc.__islocal) {
|
||||||
frm.add_custom_button(__('Send Emails'), function(){
|
frm.add_custom_button(__('Send Emails'), function(){
|
||||||
|
if (frm.is_dirty()) frappe.throw(__("Please save before proceeding."))
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
|
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
|
||||||
args: {
|
args: {
|
||||||
@ -25,7 +26,8 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
frm.add_custom_button(__('Download'), function(){
|
frm.add_custom_button(__('Download'), function(){
|
||||||
var url = frappe.urllib.get_full_url(
|
if (frm.is_dirty()) frappe.throw(__("Please save before proceeding."))
|
||||||
|
let url = frappe.urllib.get_full_url(
|
||||||
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
|
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
|
||||||
+ 'document_name='+encodeURIComponent(frm.doc.name))
|
+ 'document_name='+encodeURIComponent(frm.doc.name))
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"customers",
|
"customers",
|
||||||
"preferences",
|
"preferences",
|
||||||
"orientation",
|
"orientation",
|
||||||
|
"include_break",
|
||||||
"include_ageing",
|
"include_ageing",
|
||||||
"ageing_based_on",
|
"ageing_based_on",
|
||||||
"section_break_14",
|
"section_break_14",
|
||||||
@ -284,10 +285,16 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Terms and Conditions",
|
"label": "Terms and Conditions",
|
||||||
"options": "Terms and Conditions"
|
"options": "Terms and Conditions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "include_break",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Page Break After Each SoA"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-06 21:00:45.732505",
|
"modified": "2022-10-17 17:47:08.662475",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Process Statement Of Accounts",
|
"name": "Process Statement Of Accounts",
|
||||||
@ -321,5 +328,6 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ import copy
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.desk.reportview import get_match_cond
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import add_days, add_months, format_date, getdate, today
|
from frappe.utils import add_days, add_months, format_date, getdate, today
|
||||||
from frappe.utils.jinja import validate_template
|
from frappe.utils.jinja import validate_template
|
||||||
@ -128,7 +129,8 @@ def get_report_pdf(doc, consolidated=True):
|
|||||||
if not bool(statement_dict):
|
if not bool(statement_dict):
|
||||||
return False
|
return False
|
||||||
elif consolidated:
|
elif consolidated:
|
||||||
result = "".join(list(statement_dict.values()))
|
delimiter = '<div style="page-break-before: always;"></div>' if doc.include_break else ""
|
||||||
|
result = delimiter.join(list(statement_dict.values()))
|
||||||
return get_pdf(result, {"orientation": doc.orientation})
|
return get_pdf(result, {"orientation": doc.orientation})
|
||||||
else:
|
else:
|
||||||
for customer, statement_html in statement_dict.items():
|
for customer, statement_html in statement_dict.items():
|
||||||
@ -240,8 +242,6 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
|||||||
if int(primary_mandatory):
|
if int(primary_mandatory):
|
||||||
if primary_email == "":
|
if primary_email == "":
|
||||||
continue
|
continue
|
||||||
elif (billing_email == "") and (primary_email == ""):
|
|
||||||
continue
|
|
||||||
|
|
||||||
customer_list.append(
|
customer_list.append(
|
||||||
{"name": customer.name, "primary_email": primary_email, "billing_email": billing_email}
|
{"name": customer.name, "primary_email": primary_email, "billing_email": billing_email}
|
||||||
@ -273,8 +273,12 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr
|
|||||||
link.link_doctype='Customer'
|
link.link_doctype='Customer'
|
||||||
and link.link_name=%s
|
and link.link_name=%s
|
||||||
and contact.is_billing_contact=1
|
and contact.is_billing_contact=1
|
||||||
|
{mcond}
|
||||||
ORDER BY
|
ORDER BY
|
||||||
contact.creation desc""",
|
contact.creation desc
|
||||||
|
""".format(
|
||||||
|
mcond=get_match_cond("Contact")
|
||||||
|
),
|
||||||
customer_name,
|
customer_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -313,6 +317,8 @@ def send_emails(document_name, from_scheduler=False):
|
|||||||
attachments = [{"fname": customer + ".pdf", "fcontent": report_pdf}]
|
attachments = [{"fname": customer + ".pdf", "fcontent": report_pdf}]
|
||||||
|
|
||||||
recipients, cc = get_recipients_and_cc(customer, doc)
|
recipients, cc = get_recipients_and_cc(customer, doc)
|
||||||
|
if not recipients:
|
||||||
|
continue
|
||||||
context = get_context(customer, doc)
|
context = get_context(customer, doc)
|
||||||
subject = frappe.render_template(doc.subject, context)
|
subject = frappe.render_template(doc.subject, context)
|
||||||
message = frappe.render_template(doc.body, context)
|
message = frappe.render_template(doc.body, context)
|
||||||
|
@ -128,6 +128,12 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
|
|||||||
new_gl_map = []
|
new_gl_map = []
|
||||||
for d in gl_map:
|
for d in gl_map:
|
||||||
cost_center = d.get("cost_center")
|
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):
|
if cost_center and cost_center_allocation.get(cost_center):
|
||||||
for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
|
for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
|
||||||
gle = copy.deepcopy(d)
|
gle = copy.deepcopy(d)
|
||||||
|
@ -221,7 +221,7 @@ class TestAsset(AssetSetup):
|
|||||||
asset.precision("gross_purchase_amount"),
|
asset.precision("gross_purchase_amount"),
|
||||||
)
|
)
|
||||||
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
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"))
|
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||||
self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_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")
|
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||||
|
|
||||||
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
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"))
|
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||||
|
|
||||||
|
@ -135,6 +135,7 @@ class AssetRepair(AccountsController):
|
|||||||
"basic_rate": stock_item.valuation_rate,
|
"basic_rate": stock_item.valuation_rate,
|
||||||
"serial_no": stock_item.serial_no,
|
"serial_no": stock_item.serial_no,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
|
"project": self.project,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -101,6 +101,11 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||||
return erpnext.queries.warehouse(frm.doc);
|
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) {
|
apply_tds: function(frm) {
|
||||||
|
@ -1404,7 +1404,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
if (!r.exc && r.message) {
|
if (!r.exc && r.message) {
|
||||||
me._set_values_for_item_list(r.message);
|
me._set_values_for_item_list(r.message);
|
||||||
if(item) me.set_gross_profit(item);
|
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) {
|
for (let key in pr_row) {
|
||||||
row_to_modify[key] = pr_row[key];
|
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
|
// free_item_data is a temporary variable
|
||||||
|
@ -124,6 +124,11 @@ frappe.ui.form.on("Sales Order", {
|
|||||||
return query;
|
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'];
|
frm.ignore_doctypes_on_cancel_all = ['Purchase Order'];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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;
|
return filters;
|
||||||
}
|
}
|
||||||
|
@ -162,6 +162,12 @@ def build_filter_criterions(filters):
|
|||||||
if filters.item:
|
if filters.item:
|
||||||
qb_criterions.append(qb.DocType("Sales Order Item").item_code == 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
|
return qb_criterions
|
||||||
|
|
||||||
|
|
||||||
@ -279,11 +285,19 @@ def prepare_chart(s_orders):
|
|||||||
return chart
|
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):
|
def execute(filters=None):
|
||||||
columns = get_columns()
|
columns = get_columns()
|
||||||
sales_orders, so_invoices = get_so_with_invoices(filters)
|
sales_orders, so_invoices = get_so_with_invoices(filters)
|
||||||
sales_orders, so_invoices = set_payment_terms_statuses(sales_orders, so_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)
|
prepare_chart(sales_orders)
|
||||||
|
|
||||||
data = sales_orders
|
data = sales_orders
|
||||||
|
@ -2,7 +2,7 @@ import datetime
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
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.sales_order import make_sales_invoice
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
@ -77,12 +77,14 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
|||||||
sinv.insert()
|
sinv.insert()
|
||||||
sinv.submit()
|
sinv.submit()
|
||||||
columns, data, message, chart = execute(
|
columns, data, message, chart = execute(
|
||||||
{
|
frappe._dict(
|
||||||
"company": "_Test Company",
|
{
|
||||||
"period_start_date": "2021-06-01",
|
"company": "_Test Company",
|
||||||
"period_end_date": "2021-06-30",
|
"period_start_date": "2021-06-01",
|
||||||
"item": item.item_code,
|
"period_end_date": "2021-06-30",
|
||||||
}
|
"item": item.item_code,
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
expected_value = [
|
expected_value = [
|
||||||
@ -167,12 +169,14 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
|||||||
sinv.insert()
|
sinv.insert()
|
||||||
sinv.submit()
|
sinv.submit()
|
||||||
columns, data, message, chart = execute(
|
columns, data, message, chart = execute(
|
||||||
{
|
frappe._dict(
|
||||||
"company": "_Test Company",
|
{
|
||||||
"period_start_date": "2021-06-01",
|
"company": "_Test Company",
|
||||||
"period_end_date": "2021-06-30",
|
"period_start_date": "2021-06-01",
|
||||||
"item": item.item_code,
|
"period_end_date": "2021-06-30",
|
||||||
}
|
"item": item.item_code,
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# report defaults to company currency.
|
# report defaults to company currency.
|
||||||
@ -338,3 +342,60 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
|
|||||||
with self.subTest(filters=filters):
|
with self.subTest(filters=filters):
|
||||||
columns, data, message, chart = execute(filters)
|
columns, data, message, chart = execute(filters)
|
||||||
self.assertEqual(data, expected_values_for_group_filters[idx])
|
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)
|
||||||
|
@ -121,18 +121,24 @@ class InventoryDimension(Document):
|
|||||||
|
|
||||||
if self.apply_to_all_doctypes:
|
if self.apply_to_all_doctypes:
|
||||||
for doctype in get_inventory_documents():
|
for doctype in get_inventory_documents():
|
||||||
custom_fields.setdefault(doctype[0], dimension_fields)
|
if not field_exists(doctype[0], self.source_fieldname):
|
||||||
else:
|
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)
|
custom_fields.setdefault(self.document_type, dimension_fields)
|
||||||
|
|
||||||
if not frappe.db.get_value(
|
if not frappe.db.get_value(
|
||||||
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
|
"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 = dimension_fields[1]
|
||||||
dimension_field["fieldname"] = self.target_fieldname
|
dimension_field["fieldname"] = self.target_fieldname
|
||||||
custom_fields["Stock Ledger Entry"] = dimension_field
|
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()
|
@frappe.whitelist()
|
||||||
|
@ -191,6 +191,21 @@ class TestInventoryDimension(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(sle_rack, "Rack 1")
|
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():
|
def prepare_test_data():
|
||||||
if not frappe.db.exists("DocType", "Shelf"):
|
if not frappe.db.exists("DocType", "Shelf"):
|
||||||
|
@ -4,10 +4,13 @@ from frappe import _
|
|||||||
def get_data():
|
def get_data():
|
||||||
return {
|
return {
|
||||||
"fieldname": "material_request",
|
"fieldname": "material_request",
|
||||||
|
"internal_links": {
|
||||||
|
"Sales Order": ["items", "sales_order"],
|
||||||
|
},
|
||||||
"transactions": [
|
"transactions": [
|
||||||
{
|
{
|
||||||
"label": _("Reference"),
|
"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": _("Stock"), "items": ["Stock Entry", "Purchase Receipt", "Pick List"]},
|
||||||
{"label": _("Manufacturing"), "items": ["Work Order"]},
|
{"label": _("Manufacturing"), "items": ["Work Order"]},
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import Abs, Sum
|
||||||
from frappe.utils import flt, getdate
|
from frappe.utils import flt, getdate
|
||||||
|
|
||||||
|
|
||||||
@ -11,8 +12,6 @@ def execute(filters=None):
|
|||||||
filters = {}
|
filters = {}
|
||||||
float_precision = frappe.db.get_default("float_precision")
|
float_precision = frappe.db.get_default("float_precision")
|
||||||
|
|
||||||
condition = get_condition(filters)
|
|
||||||
|
|
||||||
avg_daily_outgoing = 0
|
avg_daily_outgoing = 0
|
||||||
diff = ((getdate(filters.get("to_date")) - getdate(filters.get("from_date"))).days) + 1
|
diff = ((getdate(filters.get("to_date")) - getdate(filters.get("from_date"))).days) + 1
|
||||||
if diff <= 0:
|
if diff <= 0:
|
||||||
@ -20,8 +19,8 @@ def execute(filters=None):
|
|||||||
|
|
||||||
columns = get_columns()
|
columns = get_columns()
|
||||||
items = get_item_info(filters)
|
items = get_item_info(filters)
|
||||||
consumed_item_map = get_consumed_items(condition)
|
consumed_item_map = get_consumed_items(filters)
|
||||||
delivered_item_map = get_delivered_items(condition)
|
delivered_item_map = get_delivered_items(filters)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for item in items:
|
for item in items:
|
||||||
@ -71,76 +70,86 @@ def get_columns():
|
|||||||
def get_item_info(filters):
|
def get_item_info(filters):
|
||||||
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
||||||
|
|
||||||
conditions = [get_item_group_condition(filters.get("item_group"))]
|
item = frappe.qb.DocType("Item")
|
||||||
if filters.get("brand"):
|
query = (
|
||||||
conditions.append("item.brand=%(brand)s")
|
frappe.qb.from_(item)
|
||||||
conditions.append("is_stock_item = 1")
|
.select(
|
||||||
|
item.name,
|
||||||
return frappe.db.sql(
|
item.item_name,
|
||||||
"""select name, item_name, description, brand, item_group,
|
item.description,
|
||||||
safety_stock, lead_time_days from `tabItem` item where {}""".format(
|
item.brand,
|
||||||
" and ".join(conditions)
|
item.item_group,
|
||||||
),
|
item.safety_stock,
|
||||||
filters,
|
item.lead_time_days,
|
||||||
as_dict=1,
|
)
|
||||||
|
.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 = [
|
purpose_to_exclude = [
|
||||||
"Material Transfer for Manufacture",
|
"Material Transfer for Manufacture",
|
||||||
"Material Transfer",
|
"Material Transfer",
|
||||||
"Send to Subcontractor",
|
"Send to Subcontractor",
|
||||||
]
|
]
|
||||||
|
|
||||||
condition += """
|
se = frappe.qb.DocType("Stock Entry")
|
||||||
and (
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
purpose is NULL
|
query = (
|
||||||
or purpose not in ({})
|
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(
|
.groupby(sle.item_code)
|
||||||
", ".join(f"'{p}'" for p in purpose_to_exclude)
|
|
||||||
)
|
)
|
||||||
condition = condition.replace("posting_date", "sle.posting_date")
|
query = get_filtered_query(filters, sle, query)
|
||||||
|
|
||||||
consumed_items = frappe.db.sql(
|
consumed_items = query.run(as_dict=True)
|
||||||
"""
|
|
||||||
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_map = {item.item_code: item.consumed_qty for item in consumed_items}
|
consumed_items_map = {item.item_code: item.consumed_qty for item in consumed_items}
|
||||||
return consumed_items_map
|
return consumed_items_map
|
||||||
|
|
||||||
|
|
||||||
def get_delivered_items(condition):
|
def get_delivered_items(filters):
|
||||||
dn_items = frappe.db.sql(
|
parent = frappe.qb.DocType("Delivery Note")
|
||||||
"""select dn_item.item_code, sum(dn_item.stock_qty) as dn_qty
|
child = frappe.qb.DocType("Delivery Note Item")
|
||||||
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
|
query = (
|
||||||
where dn.name = dn_item.parent and dn.docstatus = 1 %s
|
frappe.qb.from_(parent)
|
||||||
group by dn_item.item_code"""
|
.from_(child)
|
||||||
% (condition),
|
.select(child.item_code, Sum(child.stock_qty).as_("dn_qty"))
|
||||||
as_dict=1,
|
.where((parent.name == child.parent) & (parent.docstatus == 1))
|
||||||
|
.groupby(child.item_code)
|
||||||
)
|
)
|
||||||
|
query = get_filtered_query(filters, parent, query)
|
||||||
|
|
||||||
si_items = frappe.db.sql(
|
dn_items = query.run(as_dict=True)
|
||||||
"""select si_item.item_code, sum(si_item.stock_qty) as si_qty
|
|
||||||
from `tabSales Invoice` si, `tabSales Invoice Item` si_item
|
parent = frappe.qb.DocType("Sales Invoice")
|
||||||
where si.name = si_item.parent and si.docstatus = 1 and
|
child = frappe.qb.DocType("Sales Invoice Item")
|
||||||
si.update_stock = 1 %s
|
query = (
|
||||||
group by si_item.item_code"""
|
frappe.qb.from_(parent)
|
||||||
% (condition),
|
.from_(child)
|
||||||
as_dict=1,
|
.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 = {}
|
dn_item_map = {}
|
||||||
for item in dn_items:
|
for item in dn_items:
|
||||||
@ -152,13 +161,10 @@ def get_delivered_items(condition):
|
|||||||
return dn_item_map
|
return dn_item_map
|
||||||
|
|
||||||
|
|
||||||
def get_condition(filters):
|
def get_filtered_query(filters, table, query):
|
||||||
conditions = ""
|
|
||||||
if filters.get("from_date") and filters.get("to_date"):
|
if filters.get("from_date") and filters.get("to_date"):
|
||||||
conditions += " and posting_date between '%s' and '%s'" % (
|
query = query.where(table.posting_date.between(filters["from_date"], filters["to_date"]))
|
||||||
filters["from_date"],
|
|
||||||
filters["to_date"],
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("From and To dates required"))
|
frappe.throw(_("From and To dates are required"))
|
||||||
return conditions
|
|
||||||
|
return query
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import IfNull
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt
|
||||||
|
from pypika.terms import ExistsCriterion
|
||||||
|
|
||||||
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
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()
|
pb_details = frappe._dict()
|
||||||
item_details = frappe._dict()
|
item_details = frappe._dict()
|
||||||
|
|
||||||
conditions = get_parent_item_conditions(filters)
|
item = frappe.qb.DocType("Item")
|
||||||
parent_item_details = frappe.db.sql(
|
pb = frappe.qb.DocType("Product Bundle")
|
||||||
"""
|
|
||||||
select item.name as item_code, item.item_name, pb.description, item.item_group, item.brand, item.stock_uom
|
query = (
|
||||||
from `tabItem` item
|
frappe.qb.from_(item)
|
||||||
inner join `tabProduct Bundle` pb on pb.new_item_code = item.name
|
.inner_join(pb)
|
||||||
where ifnull(item.disabled, 0) = 0 {0}
|
.on(pb.new_item_code == item.name)
|
||||||
""".format(
|
.select(
|
||||||
conditions
|
item.name.as_("item_code"),
|
||||||
),
|
item.item_name,
|
||||||
filters,
|
pb.description,
|
||||||
as_dict=1,
|
item.item_group,
|
||||||
) # nosec
|
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 = []
|
parent_items = []
|
||||||
for d in parent_item_details:
|
for d in parent_item_details:
|
||||||
parent_items.append(d.item_code)
|
parent_items.append(d.item_code)
|
||||||
item_details[d.item_code] = d
|
item_details[d.item_code] = d
|
||||||
|
|
||||||
|
child_item_details = []
|
||||||
if parent_items:
|
if parent_items:
|
||||||
child_item_details = frappe.db.sql(
|
item = frappe.qb.DocType("Item")
|
||||||
"""
|
pb = frappe.qb.DocType("Product Bundle")
|
||||||
select
|
pbi = frappe.qb.DocType("Product Bundle Item")
|
||||||
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
|
child_item_details = (
|
||||||
from `tabProduct Bundle Item` pbi
|
frappe.qb.from_(pbi)
|
||||||
inner join `tabProduct Bundle` pb on pb.name = pbi.parent
|
.inner_join(pb)
|
||||||
inner join `tabItem` item on item.name = pbi.item_code
|
.on(pb.name == pbi.parent)
|
||||||
where pb.new_item_code in ({0})
|
.inner_join(item)
|
||||||
""".format(
|
.on(item.name == pbi.item_code)
|
||||||
", ".join(["%s"] * len(parent_items))
|
.select(
|
||||||
),
|
pb.new_item_code.as_("parent_item"),
|
||||||
parent_items,
|
pbi.item_code,
|
||||||
as_dict=1,
|
item.item_name,
|
||||||
) # nosec
|
pbi.description,
|
||||||
else:
|
item.item_group,
|
||||||
child_item_details = []
|
item.brand,
|
||||||
|
item.stock_uom,
|
||||||
|
pbi.uom,
|
||||||
|
pbi.qty,
|
||||||
|
)
|
||||||
|
.where(pb.new_item_code.isin(parent_items))
|
||||||
|
).run(as_dict=1)
|
||||||
|
|
||||||
child_items = set()
|
child_items = set()
|
||||||
for d in child_item_details:
|
for d in child_item_details:
|
||||||
@ -184,58 +208,42 @@ def get_stock_ledger_entries(filters, items):
|
|||||||
if not items:
|
if not items:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
item_conditions_sql = " and sle.item_code in ({})".format(
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
", ".join(frappe.db.escape(i) for i in items)
|
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)
|
if date := filters.get("date"):
|
||||||
|
query = query.where(sle.posting_date <= date)
|
||||||
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")
|
|
||||||
else:
|
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"))
|
frappe.throw(_("'Date' is required"))
|
||||||
|
|
||||||
conditions += " and sle.posting_date <= %s" % frappe.db.escape(filters.get("date"))
|
|
||||||
|
|
||||||
if filters.get("warehouse"):
|
if filters.get("warehouse"):
|
||||||
warehouse_details = frappe.db.get_value(
|
warehouse_details = frappe.db.get_value(
|
||||||
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
"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)
|
||||||
|
@ -305,20 +305,25 @@ def get_inventory_dimension_fields():
|
|||||||
|
|
||||||
|
|
||||||
def get_items(filters):
|
def get_items(filters):
|
||||||
|
item = frappe.qb.DocType("Item")
|
||||||
|
query = frappe.qb.from_(item).select(item.name)
|
||||||
conditions = []
|
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:
|
else:
|
||||||
if filters.get("brand"):
|
if brand := filters.get("brand"):
|
||||||
conditions.append("item.brand=%(brand)s")
|
conditions.append(item.brand == brand)
|
||||||
if filters.get("item_group"):
|
if item_group := filters.get("item_group"):
|
||||||
conditions.append(get_item_group_condition(filters.get("item_group")))
|
if condition := get_item_group_condition(item_group, item):
|
||||||
|
conditions.append(condition)
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
if conditions:
|
if conditions:
|
||||||
items = frappe.db.sql_list(
|
for condition in conditions:
|
||||||
"""select name from `tabItem` item where {}""".format(" and ".join(conditions)), filters
|
query = query.where(condition)
|
||||||
)
|
items = [r[0] for r in query.run()]
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
@ -330,29 +335,22 @@ def get_item_details(items, sl_entries, include_uom):
|
|||||||
if not items:
|
if not items:
|
||||||
return item_details
|
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:
|
if include_uom:
|
||||||
cf_field = ", ucd.conversion_factor"
|
ucd = frappe.qb.DocType("UOM Conversion Detail")
|
||||||
cf_join = (
|
query = (
|
||||||
"left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s"
|
query.left_join(ucd)
|
||||||
% frappe.db.escape(include_uom)
|
.on((ucd.parent == item.name) & (ucd.uom == include_uom))
|
||||||
|
.select(ucd.conversion_factor)
|
||||||
)
|
)
|
||||||
|
|
||||||
res = frappe.db.sql(
|
res = query.run(as_dict=True)
|
||||||
"""
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
for item in res:
|
for item in res:
|
||||||
item_details.setdefault(item.name, item)
|
item_details.setdefault(item.name, item)
|
||||||
@ -427,16 +425,28 @@ def get_warehouse_condition(warehouse):
|
|||||||
return ""
|
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)
|
item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1)
|
||||||
if item_group_details:
|
if item_group_details:
|
||||||
return (
|
if item_table:
|
||||||
"item.item_group in (select ig.name from `tabItem Group` ig \
|
ig = frappe.qb.DocType("Item Group")
|
||||||
where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"
|
return item_table.item_group.isin(
|
||||||
% (item_group_details.lft, item_group_details.rgt)
|
(
|
||||||
)
|
frappe.qb.from_(ig)
|
||||||
|
.select(ig.name)
|
||||||
return ""
|
.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:
|
def check_inventory_dimension_filters_applied(filters) -> bool:
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user