Merge pull request #39759 from frappe/version-14-hotfix

chore: release v14
This commit is contained in:
rohitwaghchaure
2024-02-07 00:13:04 +05:30
committed by GitHub
26 changed files with 528 additions and 166 deletions

View File

@@ -58,6 +58,7 @@ class Account(NestedSet):
self.validate_balance_must_be_debit_or_credit() self.validate_balance_must_be_debit_or_credit()
self.validate_account_currency() self.validate_account_currency()
self.validate_root_company_and_sync_account_to_children() self.validate_root_company_and_sync_account_to_children()
self.validate_receivable_payable_account_type()
def validate_parent(self): def validate_parent(self):
"""Fetch Parent Details and validate parent account""" """Fetch Parent Details and validate parent account"""
@@ -114,6 +115,24 @@ class Account(NestedSet):
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss" "Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
) )
def validate_receivable_payable_account_type(self):
doc_before_save = self.get_doc_before_save()
receivable_payable_types = ["Receivable", "Payable"]
if (
doc_before_save
and doc_before_save.account_type in receivable_payable_types
and doc_before_save.account_type != self.account_type
):
# check for ledger entries
if frappe.db.get_all("GL Entry", filters={"account": self.name, "is_cancelled": 0}, limit=1):
msg = _(
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
).format(
frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
)
frappe.msgprint(msg)
self.add_comment("Comment", msg)
def validate_root_details(self): def validate_root_details(self):
# does not exists parent # does not exists parent
if frappe.db.exists("Account", self.name): if frappe.db.exists("Account", self.name):

View File

@@ -13,6 +13,7 @@
"account_type", "account_type",
"account_subtype", "account_subtype",
"column_break_7", "column_break_7",
"disabled",
"is_default", "is_default",
"is_company_account", "is_company_account",
"company", "company",
@@ -199,10 +200,16 @@
"fieldtype": "Data", "fieldtype": "Data",
"in_global_search": 1, "in_global_search": 1,
"label": "Branch Code" "label": "Branch Code"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
} }
], ],
"links": [], "links": [],
"modified": "2022-05-04 15:49:42.620630", "modified": "2024-02-02 17:50:09.768835",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Account", "name": "Bank Account",

View File

@@ -9,6 +9,7 @@ from frappe.contacts.address_and_contact import (
load_address_and_contact, load_address_and_contact,
) )
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import comma_and, get_link_to_form
class BankAccount(Document): class BankAccount(Document):
@@ -25,6 +26,17 @@ class BankAccount(Document):
def validate(self): def validate(self):
self.validate_company() self.validate_company()
self.validate_iban() self.validate_iban()
self.validate_account()
def validate_account(self):
if self.account:
if accounts := frappe.db.get_all("Bank Account", filters={"account": self.account}, as_list=1):
frappe.throw(
_("'{0}' account is already used by {1}. Use another account.").format(
frappe.bold(self.account),
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
)
)
def validate_company(self): def validate_company(self):
if self.is_company_account and not self.company: if self.is_company_account and not self.company:

View File

@@ -32,8 +32,16 @@ class TestBankTransaction(FrappeTestCase):
frappe.db.delete(dt) frappe.db.delete(dt)
make_pos_profile() make_pos_profile()
add_transactions()
add_vouchers() # generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
uniq_identifier = frappe.generate_hash(length=10)
gl_account = create_gl_account("_Test Bank " + uniq_identifier)
bank_account = create_bank_account(
gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier
)
add_transactions(bank_account=bank_account)
add_vouchers(gl_account=gl_account)
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self): def test_linked_payments(self):
@@ -213,7 +221,9 @@ class TestBankTransaction(FrappeTestCase):
self.assertEqual(linked_payments[0][2], repayment_entry.name) self.assertEqual(linked_payments[0][2], repayment_entry.name)
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"): def create_bank_account(
bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
):
try: try:
frappe.get_doc( frappe.get_doc(
{ {
@@ -225,21 +235,35 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
pass pass
try: try:
frappe.get_doc( bank_account = frappe.get_doc(
{ {
"doctype": "Bank Account", "doctype": "Bank Account",
"account_name": "Checking Account", "account_name": bank_account_name,
"bank": bank_name, "bank": bank_name,
"account": account_name, "account": gl_account,
} }
).insert(ignore_if_duplicate=True) ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
return bank_account.name
def add_transactions():
create_bank_account()
def create_gl_account(gl_account_name="_Test Bank - _TC"):
gl_account = frappe.get_doc(
{
"doctype": "Account",
"company": "_Test Company",
"parent_account": "Current Assets - _TC",
"account_type": "Bank",
"is_group": 0,
"account_name": gl_account_name,
}
).insert()
return gl_account.name
def add_transactions(bank_account="_Test Bank - _TC"):
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
"doctype": "Bank Transaction", "doctype": "Bank Transaction",
@@ -247,7 +271,7 @@ def add_transactions():
"date": "2018-10-23", "date": "2018-10-23",
"deposit": 1200, "deposit": 1200,
"currency": "INR", "currency": "INR",
"bank_account": "Checking Account - Citi Bank", "bank_account": bank_account,
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -259,7 +283,7 @@ def add_transactions():
"date": "2018-10-23", "date": "2018-10-23",
"deposit": 1700, "deposit": 1700,
"currency": "INR", "currency": "INR",
"bank_account": "Checking Account - Citi Bank", "bank_account": bank_account,
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -271,7 +295,7 @@ def add_transactions():
"date": "2018-10-26", "date": "2018-10-26",
"withdrawal": 690, "withdrawal": 690,
"currency": "INR", "currency": "INR",
"bank_account": "Checking Account - Citi Bank", "bank_account": bank_account,
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -283,7 +307,7 @@ def add_transactions():
"date": "2018-10-27", "date": "2018-10-27",
"deposit": 3900, "deposit": 3900,
"currency": "INR", "currency": "INR",
"bank_account": "Checking Account - Citi Bank", "bank_account": bank_account,
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -295,13 +319,13 @@ def add_transactions():
"date": "2018-10-27", "date": "2018-10-27",
"withdrawal": 109080, "withdrawal": 109080,
"currency": "INR", "currency": "INR",
"bank_account": "Checking Account - Citi Bank", "bank_account": bank_account,
} }
).insert() ).insert()
doc.submit() doc.submit()
def add_vouchers(): def add_vouchers(gl_account="_Test Bank - _TC"):
try: try:
frappe.get_doc( frappe.get_doc(
{ {
@@ -317,7 +341,7 @@ def add_vouchers():
pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690) pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Conrad Oct 18" pe.reference_no = "Conrad Oct 18"
pe.reference_date = "2018-10-24" pe.reference_date = "2018-10-24"
pe.insert() pe.insert()
@@ -336,14 +360,14 @@ def add_vouchers():
pass pass
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200) pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Herr G Oct 18" pe.reference_no = "Herr G Oct 18"
pe.reference_date = "2018-10-24" pe.reference_date = "2018-10-24"
pe.insert() pe.insert()
pe.submit() pe.submit()
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700) pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Herr G Nov 18" pe.reference_no = "Herr G Nov 18"
pe.reference_date = "2018-11-01" pe.reference_date = "2018-11-01"
pe.insert() pe.insert()
@@ -374,10 +398,10 @@ def add_vouchers():
pass pass
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1) pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1)
pi.cash_bank_account = "_Test Bank - _TC" pi.cash_bank_account = gl_account
pi.insert() pi.insert()
pi.submit() pi.submit()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Poore Simon's Oct 18" pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28" pe.reference_date = "2018-10-28"
pe.paid_amount = 690 pe.paid_amount = 690
@@ -386,7 +410,7 @@ def add_vouchers():
pe.submit() pe.submit()
si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900) si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account)
pe.reference_no = "Poore Simon's Oct 18" pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28" pe.reference_date = "2018-10-28"
pe.insert() pe.insert()
@@ -409,16 +433,12 @@ def add_vouchers():
if not frappe.db.get_value( if not frappe.db.get_value(
"Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"} "Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
): ):
mode_of_payment.append( mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
"accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"}
)
mode_of_payment.save() mode_of_payment.save()
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1) si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
si.is_pos = 1 si.is_pos = 1
si.append( si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080})
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080}
)
si.insert() si.insert()
si.submit() si.submit()

View File

@@ -13,16 +13,9 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts, get_checks_for_pl_and_bs_accounts,
) )
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
)
from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency
from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.exceptions import ( from erpnext.exceptions import InvalidAccountCurrency
InvalidAccountCurrency,
InvalidAccountDimensionError,
MandatoryAccountDimensionError,
)
exclude_from_linked_with = True exclude_from_linked_with = True
@@ -54,7 +47,6 @@ class GLEntry(Document):
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher": if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
self.validate_account_details(adv_adj) self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs() self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()
validate_balance_type(self.account, adv_adj) validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj) validate_frozen_account(self.account, adv_adj)
@@ -164,42 +156,6 @@ class GLEntry(Document):
) )
) )
def validate_allowed_dimensions(self):
dimension_filter_map = get_dimension_filter_map()
for key, value in dimension_filter_map.items():
dimension = key[0]
account = key[1]
if self.account == account:
if value["is_mandatory"] and not self.get(dimension):
frappe.throw(
_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
),
MandatoryAccountDimensionError,
)
if value["allow_or_restrict"] == "Allow":
if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
else:
if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
def check_pl_account(self): def check_pl_account(self):
if ( if (
self.is_opening == "Yes" self.is_opening == "Yes"

View File

@@ -4,9 +4,13 @@
import unittest import unittest
import frappe import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import getdate from frappe.utils import getdate
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
create_bank_account,
create_gl_account,
)
from erpnext.accounts.doctype.payment_entry.payment_entry import ( from erpnext.accounts.doctype.payment_entry.payment_entry import (
get_payment_entry, get_payment_entry,
make_payment_order, make_payment_order,
@@ -14,28 +18,32 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
class TestPaymentOrder(unittest.TestCase): class TestPaymentOrder(FrappeTestCase):
def setUp(self): def setUp(self):
create_bank_account() # generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
uniq_identifier = frappe.generate_hash(length=10)
self.gl_account = create_gl_account("_Test Bank " + uniq_identifier)
self.bank_account = create_bank_account(
gl_account=self.gl_account, bank_account_name="Checking Account " + uniq_identifier
)
def tearDown(self): def tearDown(self):
for bt in frappe.get_all("Payment Order"): frappe.db.rollback()
doc = frappe.get_doc("Payment Order", bt.name)
doc.cancel()
doc.delete()
def test_payment_order_creation_against_payment_entry(self): def test_payment_order_creation_against_payment_entry(self):
purchase_invoice = make_purchase_invoice() purchase_invoice = make_purchase_invoice()
payment_entry = get_payment_entry( payment_entry = get_payment_entry(
"Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC" "Purchase Invoice", purchase_invoice.name, bank_account=self.gl_account
) )
payment_entry.reference_no = "_Test_Payment_Order" payment_entry.reference_no = "_Test_Payment_Order"
payment_entry.reference_date = getdate() payment_entry.reference_date = getdate()
payment_entry.party_bank_account = "Checking Account - Citi Bank" payment_entry.party_bank_account = self.bank_account
payment_entry.insert() payment_entry.insert()
payment_entry.submit() payment_entry.submit()
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry") doc = create_payment_order_against_payment_entry(
payment_entry, "Payment Entry", self.bank_account
)
reference_doc = doc.get("references")[0] reference_doc = doc.get("references")[0]
self.assertEqual(reference_doc.reference_name, payment_entry.name) self.assertEqual(reference_doc.reference_name, payment_entry.name)
self.assertEqual(reference_doc.reference_doctype, "Payment Entry") self.assertEqual(reference_doc.reference_doctype, "Payment Entry")
@@ -43,13 +51,13 @@ class TestPaymentOrder(unittest.TestCase):
self.assertEqual(reference_doc.amount, 250) self.assertEqual(reference_doc.amount, 250)
def create_payment_order_against_payment_entry(ref_doc, order_type): def create_payment_order_against_payment_entry(ref_doc, order_type, bank_account):
payment_order = frappe.get_doc( payment_order = frappe.get_doc(
dict( dict(
doctype="Payment Order", doctype="Payment Order",
company="_Test Company", company="_Test Company",
payment_order_type=order_type, payment_order_type=order_type,
company_bank_account="Checking Account - Citi Bank", company_bank_account=bank_account,
) )
) )
doc = make_payment_order(ref_doc.name, payment_order) doc = make_payment_order(ref_doc.name, payment_order)

View File

@@ -64,18 +64,6 @@ def get_statement_dict(doc, get_statement_dict=False):
statement_dict = {} statement_dict = {}
ageing = "" ageing = ""
err_journals = None
if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals:
err_journals = frappe.db.get_all(
"Journal Entry",
filters={
"company": doc.company,
"docstatus": 1,
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
},
as_list=True,
)
for entry in doc.customers: for entry in doc.customers:
if doc.include_ageing: if doc.include_ageing:
ageing = set_ageing(doc, entry) ageing = set_ageing(doc, entry)
@@ -88,8 +76,8 @@ def get_statement_dict(doc, get_statement_dict=False):
) )
filters = get_common_filters(doc) filters = get_common_filters(doc)
if err_journals: if doc.ignore_exchange_rate_revaluation_journals:
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]}) filters.update({"ignore_err": True})
if doc.report == "General Ledger": if doc.report == "General Ledger":
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency)) filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))

View File

@@ -13,9 +13,13 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions, get_accounting_dimensions,
) )
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
)
from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.utils import create_payment_ledger_entry from erpnext.accounts.utils import create_payment_ledger_entry
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
def make_gl_entries( def make_gl_entries(
@@ -354,6 +358,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
process_debit_credit_difference(gl_map) process_debit_credit_difference(gl_map)
dimension_filter_map = get_dimension_filter_map()
if gl_map: if gl_map:
check_freezing_date(gl_map[0]["posting_date"], adv_adj) check_freezing_date(gl_map[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_map) is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
@@ -361,6 +366,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"]) validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
for entry in gl_map: for entry in gl_map:
validate_allowed_dimensions(entry, dimension_filter_map)
make_entry(entry, adv_adj, update_outstanding, from_repost) make_entry(entry, adv_adj, update_outstanding, from_repost)
@@ -672,3 +678,39 @@ def set_as_cancel(voucher_type, voucher_no):
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""", where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
(now(), frappe.session.user, voucher_type, voucher_no), (now(), frappe.session.user, voucher_type, voucher_no),
) )
def validate_allowed_dimensions(gl_entry, dimension_filter_map):
for key, value in dimension_filter_map.items():
dimension = key[0]
account = key[1]
if gl_entry.account == account:
if value["is_mandatory"] and not gl_entry.get(dimension):
frappe.throw(
_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(gl_entry.account)
),
MandatoryAccountDimensionError,
)
if value["allow_or_restrict"] == "Allow":
if gl_entry.get(dimension) and gl_entry.get(dimension) not in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(gl_entry.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(gl_entry.account),
),
InvalidAccountDimensionError,
)
else:
if gl_entry.get(dimension) and gl_entry.get(dimension) in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(gl_entry.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(gl_entry.account),
),
InvalidAccountDimensionError,
)

View File

@@ -193,8 +193,14 @@ frappe.query_reports["General Ledger"] = {
"fieldname": "show_remarks", "fieldname": "show_remarks",
"label": __("Show Remarks"), "label": __("Show Remarks"),
"fieldtype": "Check" "fieldtype": "Check"
},
{
"fieldname": "ignore_err",
"label": __("Ignore Exchange Rate Revaluation Journals"),
"fieldtype": "Check"
} }
] ]
} }

View File

@@ -231,6 +231,19 @@ def get_conditions(filters):
if filters.get("voucher_no"): if filters.get("voucher_no"):
conditions.append("voucher_no=%(voucher_no)s") conditions.append("voucher_no=%(voucher_no)s")
if filters.get("ignore_err"):
err_journals = frappe.db.get_all(
"Journal Entry",
filters={
"company": filters.get("company"),
"docstatus": 1,
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
},
as_list=True,
)
if err_journals:
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
if filters.get("voucher_no_not_in"): if filters.get("voucher_no_not_in"):
conditions.append("voucher_no not in %(voucher_no_not_in)s") conditions.append("voucher_no not in %(voucher_no_not_in)s")

View File

@@ -3,7 +3,7 @@
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
from frappe.utils import today from frappe.utils import flt, today
from erpnext.accounts.report.general_ledger.general_ledger import execute from erpnext.accounts.report.general_ledger.general_ledger import execute
@@ -148,3 +148,105 @@ class TestGeneralLedger(FrappeTestCase):
self.assertEqual(data[2]["credit"], 900) self.assertEqual(data[2]["credit"], 900)
self.assertEqual(data[3]["debit"], 100) self.assertEqual(data[3]["debit"], 100)
self.assertEqual(data[3]["credit"], 100) self.assertEqual(data[3]["credit"], 100)
def test_ignore_exchange_rate_journals_filter(self):
# create a new account with USD currency
account_name = "Test Debtors USD"
company = "_Test Company"
account = frappe.get_doc(
{
"account_name": account_name,
"is_group": 0,
"company": company,
"root_type": "Asset",
"report_type": "Balance Sheet",
"account_currency": "USD",
"parent_account": "Accounts Receivable - _TC",
"account_type": "Receivable",
"doctype": "Account",
}
)
account.insert(ignore_if_duplicate=True)
# create a JV to debit 1000 USD at 75 exchange rate
jv = frappe.new_doc("Journal Entry")
jv.posting_date = today()
jv.company = company
jv.multi_currency = 1
jv.cost_center = "_Test Cost Center - _TC"
jv.set(
"accounts",
[
{
"account": account.name,
"party_type": "Customer",
"party": "_Test Customer USD",
"debit_in_account_currency": 1000,
"credit_in_account_currency": 0,
"exchange_rate": 75,
"cost_center": "_Test Cost Center - _TC",
},
{
"account": "Cash - _TC",
"debit_in_account_currency": 0,
"credit_in_account_currency": 75000,
"cost_center": "_Test Cost Center - _TC",
},
],
)
jv.save()
jv.submit()
revaluation = frappe.new_doc("Exchange Rate Revaluation")
revaluation.posting_date = today()
revaluation.company = company
accounts = revaluation.get_accounts_data()
revaluation.extend("accounts", accounts)
row = revaluation.accounts[0]
row.new_exchange_rate = 83
row.new_balance_in_base_currency = flt(
row.new_exchange_rate * flt(row.balance_in_account_currency)
)
row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
revaluation.set_total_gain_loss()
revaluation = revaluation.save().submit()
# post journal entry for Revaluation doc
frappe.db.set_value(
"Company", company, "unrealized_exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"
)
revaluation_jv = revaluation.make_jv_for_revaluation()
revaluation_jv.cost_center = "_Test Cost Center - _TC"
for acc in revaluation_jv.get("accounts"):
acc.cost_center = "_Test Cost Center - _TC"
revaluation_jv.save()
revaluation_jv.submit()
# With ignore_err enabled
columns, data = execute(
frappe._dict(
{
"company": company,
"from_date": today(),
"to_date": today(),
"account": [account.name],
"group_by": "Group by Voucher (Consolidated)",
"ignore_err": True,
}
)
)
self.assertNotIn(revaluation_jv.name, set([x.voucher_no for x in data]))
# Without ignore_err enabled
columns, data = execute(
frappe._dict(
{
"company": company,
"from_date": today(),
"to_date": today(),
"account": [account.name],
"group_by": "Group by Voucher (Consolidated)",
"ignore_err": False,
}
)
)
self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))

View File

@@ -317,6 +317,7 @@ class PurchaseOrder(BuyingController):
self.update_requested_qty() self.update_requested_qty()
self.update_ordered_qty() self.update_ordered_qty()
self.update_reserved_qty_for_subcontract() self.update_reserved_qty_for_subcontract()
self.update_blanket_order()
self.notify_update() self.notify_update()
clear_doctype_notifications(self) clear_doctype_notifications(self)
@@ -456,6 +457,7 @@ class PurchaseOrder(BuyingController):
) )
@frappe.request_cache
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0): def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
"""get last purchase rate for an item""" """get last purchase rate for an item"""

View File

@@ -814,6 +814,30 @@ class TestPurchaseOrder(FrappeTestCase):
# To test if the PO does NOT have a Blanket Order # To test if the PO does NOT have a Blanket Order
self.assertEqual(po_doc.items[0].blanket_order, None) self.assertEqual(po_doc.items[0].blanket_order, None)
def test_blanket_order_on_po_close_and_open(self):
# Step - 1: Create Blanket Order
bo = make_blanket_order(blanket_order_type="Purchasing", quantity=10, rate=10)
# Step - 2: Create Purchase Order
po = create_purchase_order(
item_code="_Test Item", qty=5, against_blanket_order=1, against_blanket=bo.name
)
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 5)
# Step - 3: Close Purchase Order
po.update_status("Closed")
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 0)
# Step - 4: Re-Open Purchase Order
po.update_status("Re-open")
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 5)
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template, create_payment_terms_template,
@@ -1016,6 +1040,7 @@ def create_purchase_order(**args):
"schedule_date": add_days(nowdate(), 1), "schedule_date": add_days(nowdate(), 1),
"include_exploded_items": args.get("include_exploded_items", 1), "include_exploded_items": args.get("include_exploded_items", 1),
"against_blanket_order": args.against_blanket_order, "against_blanket_order": args.against_blanket_order,
"against_blanket": args.against_blanket,
"material_request": args.material_request, "material_request": args.material_request,
"material_request_item": args.material_request_item, "material_request_item": args.material_request_item,
}, },

View File

@@ -546,7 +546,6 @@
"fieldname": "blanket_order", "fieldname": "blanket_order",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Blanket Order", "label": "Blanket Order",
"no_copy": 1,
"options": "Blanket Order" "options": "Blanket Order"
}, },
{ {
@@ -554,7 +553,6 @@
"fieldname": "blanket_order_rate", "fieldname": "blanket_order_rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Blanket Order Rate", "label": "Blanket Order Rate",
"no_copy": 1,
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@@ -918,7 +916,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-11-24 19:07:34.921094", "modified": "2024-02-05 11:23:24.859435",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item", "name": "Purchase Order Item",

View File

@@ -671,7 +671,7 @@ class AccountsController(TransactionBase):
if self.get("is_subcontracted"): if self.get("is_subcontracted"):
args["is_subcontracted"] = self.is_subcontracted args["is_subcontracted"] = self.is_subcontracted
ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False) ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
for fieldname, value in ret.items(): for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None: if item.meta.get_field(fieldname) and value is not None:

View File

@@ -679,17 +679,24 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
conditions, bin_conditions = [], [] conditions, bin_conditions = [], []
filter_dict = get_doctype_wise_filters(filters) filter_dict = get_doctype_wise_filters(filters)
query = """select `tabWarehouse`.name, warehouse_field = "name"
meta = frappe.get_meta("Warehouse")
if meta.get("show_title_field_in_link") and meta.get("title_field"):
searchfield = meta.get("title_field")
warehouse_field = meta.get("title_field")
query = """select `tabWarehouse`.`{warehouse_field}`,
CONCAT_WS(' : ', 'Actual Qty', ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty CONCAT_WS(' : ', 'Actual Qty', ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty
from `tabWarehouse` left join `tabBin` from `tabWarehouse` left join `tabBin`
on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions} on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions}
where where
`tabWarehouse`.`{key}` like {txt} `tabWarehouse`.`{key}` like {txt}
{fcond} {mcond} {fcond} {mcond}
order by ifnull(`tabBin`.actual_qty, 0) desc order by ifnull(`tabBin`.actual_qty, 0) desc, `tabWarehouse`.`{warehouse_field}` asc
limit limit
{page_len} offset {start} {page_len} offset {start}
""".format( """.format(
warehouse_field=warehouse_field,
bin_conditions=get_filters_cond( bin_conditions=get_filters_cond(
doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True
), ),

View File

@@ -586,7 +586,7 @@ class SellingController(StockController):
if self.doctype in ["Sales Order", "Quotation"]: if self.doctype in ["Sales Order", "Quotation"]:
for item in self.items: for item in self.items:
item.gross_profit = flt( item.gross_profit = flt(
((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item) ((item.base_rate - flt(item.valuation_rate)) * item.stock_qty), self.precision("amount", item)
) )
def set_customer_address(self): def set_customer_address(self):

View File

@@ -746,14 +746,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
} }
let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"]; let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"];
if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") && if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
selling_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) { selling_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
me.frm.set_value("tc_name", company_doc.default_selling_terms); me.frm.set_value("tc_name", company_doc.default_selling_terms);
} }
let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order", let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order",
"Material Request", "Purchase Receipt"]; "Material Request", "Purchase Receipt"];
// Purchase Invoice is excluded as per issue #3345 // Purchase Invoice is excluded as per issue #3345
if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") && if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
buying_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) { buying_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
me.frm.set_value("tc_name", company_doc.default_buying_terms); me.frm.set_value("tc_name", company_doc.default_buying_terms);
} }

View File

@@ -171,6 +171,7 @@ class Customer(TransactionBase):
if self.flags.is_new_doc: if self.flags.is_new_doc:
self.link_lead_address_and_contact() self.link_lead_address_and_contact()
self.copy_communication()
self.update_customer_groups() self.update_customer_groups()
@@ -224,6 +225,17 @@ class Customer(TransactionBase):
linked_doc.append("links", dict(link_doctype="Customer", link_name=self.name)) linked_doc.append("links", dict(link_doctype="Customer", link_name=self.name))
linked_doc.save(ignore_permissions=self.flags.ignore_permissions) linked_doc.save(ignore_permissions=self.flags.ignore_permissions)
def copy_communication(self):
if not self.lead_name or not frappe.db.get_single_value(
"CRM Settings", "carry_forward_communication_and_comments"
):
return
from erpnext.crm.utils import copy_comments, link_communications
copy_comments("Lead", self.lead_name, self)
link_communications("Lead", self.lead_name, self)
def validate_name_with_customer_group(self): def validate_name_with_customer_group(self):
if frappe.db.exists("Customer Group", self.name): if frappe.db.exists("Customer Group", self.name):
frappe.throw( frappe.throw(

View File

@@ -122,6 +122,13 @@ class LandedCostVoucher(Document):
self.get("items")[item_count - 1].applicable_charges += diff self.get("items")[item_count - 1].applicable_charges += diff
def validate_applicable_charges_for_item(self): def validate_applicable_charges_for_item(self):
if self.distribute_charges_based_on == "Distribute Manually" and len(self.taxes) > 1:
frappe.throw(
_(
"Please keep one Applicable Charges, when 'Distribute Charges Based On' is 'Distribute Manually'. For more charges, please create another Landed Cost Voucher."
)
)
based_on = self.distribute_charges_based_on.lower() based_on = self.distribute_charges_based_on.lower()
if based_on != "distribute manually": if based_on != "distribute manually":

View File

@@ -417,6 +417,7 @@ def make_purchase_order(source_name, target_doc=None, args=None):
postprocess, postprocess,
) )
doclist.set_onload("load_after_mapping", False)
return doclist return doclist

View File

@@ -1200,16 +1200,16 @@ def get_item_account_wise_additional_cost(purchase_document):
for lcv in landed_cost_vouchers: for lcv in landed_cost_vouchers:
landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent) landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent)
based_on_field = None
# Use amount field for total item cost for manually cost distributed LCVs # Use amount field for total item cost for manually cost distributed LCVs
if landed_cost_voucher_doc.distribute_charges_based_on == "Distribute Manually": if landed_cost_voucher_doc.distribute_charges_based_on != "Distribute Manually":
based_on_field = "amount"
else:
based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on) based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
total_item_cost = 0 total_item_cost = 0
for item in landed_cost_voucher_doc.items: if based_on_field:
total_item_cost += item.get(based_on_field) for item in landed_cost_voucher_doc.items:
total_item_cost += item.get(based_on_field)
for item in landed_cost_voucher_doc.items: for item in landed_cost_voucher_doc.items:
if item.receipt_document == purchase_document: if item.receipt_document == purchase_document:

View File

@@ -1734,6 +1734,48 @@ class TestStockEntry(FrappeTestCase):
self.assertFalse(doc.is_enqueue_action()) self.assertFalse(doc.is_enqueue_action())
frappe.flags.in_test = True frappe.flags.in_test = True
def test_auto_reorder_level(self):
from erpnext.stock.reorder_item import reorder_item
item_doc = make_item(
"Test Auto Reorder Item - 001",
properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1},
uoms=[{"uom": "Nos", "conversion_factor": 5}],
)
if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}):
item_doc.append(
"reorder_levels",
{
"warehouse_reorder_level": 0,
"warehouse_reorder_qty": 10,
"warehouse": "_Test Warehouse - _TC",
"material_request_type": "Purchase",
},
)
item_doc.save(ignore_permissions=True)
frappe.db.set_single_value("Stock Settings", "auto_indent", 1)
mr_list = reorder_item()
frappe.db.set_single_value("Stock Settings", "auto_indent", 0)
mrs = frappe.get_all(
"Material Request Item",
fields=["qty", "stock_uom", "stock_qty"],
filters={"item_code": item_doc.name, "uom": "Nos"},
)
for mri in mrs:
self.assertEqual(mri.stock_uom, "Kg")
self.assertEqual(mri.stock_qty, 10)
self.assertEqual(mri.qty, 2)
for mr in mr_list:
mr.cancel()
mr.delete()
def make_serialized_item(**args): def make_serialized_item(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

@@ -87,7 +87,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
get_party_item_code(args, item, out) get_party_item_code(args, item, out)
set_valuation_rate(out, args) if args.get("doctype") in ["Sales Order", "Quotation"]:
set_valuation_rate(out, args)
update_party_blanket_order(args, out) update_party_blanket_order(args, out)
@@ -303,7 +304,9 @@ def get_basic_details(args, item, overwrite_warehouse=True):
if not item: if not item:
item = frappe.get_doc("Item", args.get("item_code")) item = frappe.get_doc("Item", args.get("item_code"))
if item.variant_of and not item.taxes: if (
item.variant_of and not item.taxes and frappe.db.exists("Item Tax", {"parent": item.variant_of})
):
item.update_template_tables() item.update_template_tables()
item_defaults = get_item_defaults(item.name, args.company) item_defaults = get_item_defaults(item.name, args.company)

View File

@@ -34,73 +34,157 @@ def _reorder_item():
erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0] erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0]
) )
items_to_consider = frappe.db.sql_list( items_to_consider = get_items_for_reorder()
"""select name from `tabItem` item
where is_stock_item=1 and has_variants=0
and disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
and (exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
or (variant_of is not null and variant_of != ''
and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of))
)""",
{"today": nowdate()},
)
if not items_to_consider: if not items_to_consider:
return return
item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider) item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider)
def add_to_material_request( def add_to_material_request(**kwargs):
item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None if isinstance(kwargs, dict):
): kwargs = frappe._dict(kwargs)
if warehouse not in warehouse_company:
if kwargs.warehouse not in warehouse_company:
# a disabled warehouse # a disabled warehouse
return return
reorder_level = flt(reorder_level) reorder_level = flt(kwargs.reorder_level)
reorder_qty = flt(reorder_qty) reorder_qty = flt(kwargs.reorder_qty)
# projected_qty will be 0 if Bin does not exist # projected_qty will be 0 if Bin does not exist
if warehouse_group: if kwargs.warehouse_group:
projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse_group)) projected_qty = flt(
item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse_group)
)
else: else:
projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse)) projected_qty = flt(
item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse)
)
if (reorder_level or reorder_qty) and projected_qty < reorder_level: if (reorder_level or reorder_qty) and projected_qty < reorder_level:
deficiency = reorder_level - projected_qty deficiency = reorder_level - projected_qty
if deficiency > reorder_qty: if deficiency > reorder_qty:
reorder_qty = deficiency reorder_qty = deficiency
company = warehouse_company.get(warehouse) or default_company company = warehouse_company.get(kwargs.warehouse) or default_company
material_requests[material_request_type].setdefault(company, []).append( material_requests[kwargs.material_request_type].setdefault(company, []).append(
{"item_code": item_code, "warehouse": warehouse, "reorder_qty": reorder_qty} {
"item_code": kwargs.item_code,
"warehouse": kwargs.warehouse,
"reorder_qty": reorder_qty,
"item_details": kwargs.item_details,
}
) )
for item_code in items_to_consider: for item_code, reorder_levels in items_to_consider.items():
item = frappe.get_doc("Item", item_code) for d in reorder_levels:
if d.has_variants:
continue
if item.variant_of and not item.get("reorder_levels"): add_to_material_request(
item.update_template_tables() item_code=item_code,
warehouse=d.warehouse,
if item.get("reorder_levels"): reorder_level=d.warehouse_reorder_level,
for d in item.get("reorder_levels"): reorder_qty=d.warehouse_reorder_qty,
add_to_material_request( material_request_type=d.material_request_type,
item_code, warehouse_group=d.warehouse_group,
d.warehouse, item_details=frappe._dict(
d.warehouse_reorder_level, {
d.warehouse_reorder_qty, "item_code": item_code,
d.material_request_type, "name": item_code,
warehouse_group=d.warehouse_group, "item_name": d.item_name,
) "item_group": d.item_group,
"brand": d.brand,
"description": d.description,
"stock_uom": d.stock_uom,
"purchase_uom": d.purchase_uom,
}
),
)
if material_requests: if material_requests:
return create_material_request(material_requests) return create_material_request(material_requests)
def get_items_for_reorder() -> dict[str, list]:
reorder_table = frappe.qb.DocType("Item Reorder")
item_table = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(reorder_table)
.inner_join(item_table)
.on(reorder_table.parent == item_table.name)
.select(
reorder_table.warehouse,
reorder_table.warehouse_group,
reorder_table.material_request_type,
reorder_table.warehouse_reorder_level,
reorder_table.warehouse_reorder_qty,
item_table.name,
item_table.stock_uom,
item_table.purchase_uom,
item_table.description,
item_table.item_name,
item_table.item_group,
item_table.brand,
item_table.variant_of,
item_table.has_variants,
)
.where(
(item_table.disabled == 0)
& (item_table.is_stock_item == 1)
& (
(item_table.end_of_life.isnull())
| (item_table.end_of_life > nowdate())
| (item_table.end_of_life == "0000-00-00")
)
)
)
data = query.run(as_dict=True)
itemwise_reorder = frappe._dict({})
for d in data:
itemwise_reorder.setdefault(d.name, []).append(d)
itemwise_reorder = get_reorder_levels_for_variants(itemwise_reorder)
return itemwise_reorder
def get_reorder_levels_for_variants(itemwise_reorder):
item_table = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(item_table)
.select(
item_table.name,
item_table.variant_of,
)
.where(
(item_table.disabled == 0)
& (item_table.is_stock_item == 1)
& (
(item_table.end_of_life.isnull())
| (item_table.end_of_life > nowdate())
| (item_table.end_of_life == "0000-00-00")
)
& (item_table.variant_of.notnull())
)
)
variants_item = query.run(as_dict=True)
for row in variants_item:
if not itemwise_reorder.get(row.name) and itemwise_reorder.get(row.variant_of):
itemwise_reorder.setdefault(row.name, []).extend(itemwise_reorder.get(row.variant_of, []))
return itemwise_reorder
def get_item_warehouse_projected_qty(items_to_consider): def get_item_warehouse_projected_qty(items_to_consider):
item_warehouse_projected_qty = {} item_warehouse_projected_qty = {}
items_to_consider = list(items_to_consider.keys())
for item_code, warehouse, projected_qty in frappe.db.sql( for item_code, warehouse, projected_qty in frappe.db.sql(
"""select item_code, warehouse, projected_qty """select item_code, warehouse, projected_qty
@@ -164,7 +248,7 @@ def create_material_request(material_requests):
for d in items: for d in items:
d = frappe._dict(d) d = frappe._dict(d)
item = frappe.get_doc("Item", d.item_code) item = d.get("item_details")
uom = item.stock_uom uom = item.stock_uom
conversion_factor = 1.0 conversion_factor = 1.0
@@ -190,6 +274,7 @@ def create_material_request(material_requests):
"item_code": d.item_code, "item_code": d.item_code,
"schedule_date": add_days(nowdate(), cint(item.lead_time_days)), "schedule_date": add_days(nowdate(), cint(item.lead_time_days)),
"qty": qty, "qty": qty,
"conversion_factor": conversion_factor,
"uom": uom, "uom": uom,
"stock_uom": item.stock_uom, "stock_uom": item.stock_uom,
"warehouse": d.warehouse, "warehouse": d.warehouse,

View File

@@ -90,8 +90,7 @@ class StockBalanceReport(object):
self.opening_data.setdefault(group_by_key, entry) self.opening_data.setdefault(group_by_key, entry)
def prepare_new_data(self): def prepare_new_data(self):
if not self.sle_entries: self.item_warehouse_map = self.get_item_warehouse_map()
return
if self.filters.get("show_stock_ageing_data"): if self.filters.get("show_stock_ageing_data"):
self.filters["show_warehouse_wise_stock"] = True self.filters["show_warehouse_wise_stock"] = True
@@ -99,7 +98,7 @@ class StockBalanceReport(object):
_func = itemgetter(1) _func = itemgetter(1)
self.item_warehouse_map = self.get_item_warehouse_map() del self.sle_entries
variant_values = {} variant_values = {}
if self.filters.get("show_variant_attributes"): if self.filters.get("show_variant_attributes"):
@@ -139,15 +138,22 @@ class StockBalanceReport(object):
item_warehouse_map = {} item_warehouse_map = {}
self.opening_vouchers = self.get_opening_vouchers() self.opening_vouchers = self.get_opening_vouchers()
for entry in self.sle_entries: if self.filters.get("show_stock_ageing_data"):
group_by_key = self.get_group_by_key(entry) self.sle_entries = self.sle_query.run(as_dict=True)
if group_by_key not in item_warehouse_map:
self.initialize_data(item_warehouse_map, group_by_key, entry)
self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key) with frappe.db.unbuffered_cursor():
if not self.filters.get("show_stock_ageing_data"):
self.sle_entries = self.sle_query.run(as_dict=True, as_iterator=True)
if self.opening_data.get(group_by_key): for entry in self.sle_entries:
del self.opening_data[group_by_key] group_by_key = self.get_group_by_key(entry)
if group_by_key not in item_warehouse_map:
self.initialize_data(item_warehouse_map, group_by_key, entry)
self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key)
if self.opening_data.get(group_by_key):
del self.opening_data[group_by_key]
for group_by_key, entry in self.opening_data.items(): for group_by_key, entry in self.opening_data.items():
if group_by_key not in item_warehouse_map: if group_by_key not in item_warehouse_map:
@@ -236,7 +242,8 @@ class StockBalanceReport(object):
.where( .where(
(table.docstatus == 1) (table.docstatus == 1)
& (table.company == self.filters.company) & (table.company == self.filters.company)
& ((table.to_date <= self.from_date)) & (table.to_date <= self.from_date)
& (table.status == "Completed")
) )
.orderby(table.to_date, order=Order.desc) .orderby(table.to_date, order=Order.desc)
.limit(1) .limit(1)
@@ -289,7 +296,7 @@ class StockBalanceReport(object):
if self.filters.get("company"): if self.filters.get("company"):
query = query.where(sle.company == self.filters.get("company")) query = query.where(sle.company == self.filters.get("company"))
self.sle_entries = query.run(as_dict=True) self.sle_query = query
def apply_inventory_dimensions_filters(self, query, sle) -> str: def apply_inventory_dimensions_filters(self, query, sle) -> str:
inventory_dimension_fields = self.get_inventory_dimension_fields() inventory_dimension_fields = self.get_inventory_dimension_fields()