Compare commits

..

1 Commits

Author SHA1 Message Date
Ankush Menat
41f44ba1c6 perf: cache journal entry balances for 5 minutes
If user is repeatedly adding multiple JV lines then everytime the value
will be refetched from database, reading GLE table can be very expensive
on large DBs.

AFAIK this function is only for UX and balances are anyways overidden
when saving the document.

Proper fix: maybe use closing balance here?
2024-03-02 15:53:15 +05:30
29 changed files with 292 additions and 555 deletions

View File

@@ -3,7 +3,7 @@ import inspect
import frappe
__version__ = "15.16.0"
__version__ = "15.14.3"
def get_default_company(user=None):

View File

@@ -11,10 +11,6 @@ from frappe.model import core_doctypes_list
from frappe.model.document import Document
from frappe.utils import cstr
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
get_allowed_types_from_settings,
)
class AccountingDimension(Document):
# begin: auto-generated types
@@ -110,7 +106,6 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
doc_count = len(get_accounting_dimensions())
count = 0
repostable_doctypes = get_allowed_types_from_settings()
for doctype in doclist:
@@ -126,7 +121,6 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
"options": doc.document_type,
"insert_after": insert_after_field,
"owner": "Administrator",
"allow_on_submit": 1 if doctype in repostable_doctypes else 0,
}
meta = frappe.get_meta(doctype, cached=False)

View File

@@ -3,21 +3,16 @@
frappe.ui.form.on('Cost Center Allocation', {
setup: function(frm) {
let filters = {"is_group": 0};
if (frm.doc.company) {
$.extend(filters, {
"company": frm.doc.company
});
}
frm.set_query('main_cost_center', function() {
return {
filters: {
company: frm.doc.company,
is_group: 0
}
};
});
frm.set_query('cost_center', 'allocation_percentages', function() {
return {
filters: {
company: frm.doc.company,
is_group: 0
}
filters: filters
};
});
}

View File

@@ -14,25 +14,6 @@ frappe.ui.form.on("Journal Entry", {
refresh: function(frm) {
erpnext.toggle_naming_series();
if (frm.doc.repost_required && frm.doc.docstatus===1) {
frm.set_intro(__("Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update."));
frm.add_custom_button(__('Repost Accounting Entries'),
() => {
frm.call({
doc: frm.doc,
method: 'repost_accounting_entries',
freeze: true,
freeze_message: __('Reposting...'),
callback: (r) => {
if (!r.exc) {
frappe.msgprint(__('Accounting Entries are reposted.'));
frm.refresh();
}
}
});
}).removeClass('btn-default').addClass('btn-warning');
}
if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Ledger'), function() {
frappe.route_options = {
@@ -203,6 +184,7 @@ var update_jv_details = function(doc, r) {
$.each(r, function(i, d) {
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
frappe.model.set_value(row.doctype, row.name, "account", d.account)
frappe.model.set_value(row.doctype, row.name, "balance", d.balance)
});
refresh_field("accounts");
}
@@ -211,6 +193,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
onload() {
this.load_defaults();
this.setup_queries();
this.setup_balance_formatter();
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}
@@ -309,6 +292,19 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
}
setup_balance_formatter() {
const formatter = function(value, df, options, doc) {
var currency = frappe.meta.get_field_currency(df, doc);
var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
return "<div style='text-align: right'>"
+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
+ " " + dr_or_cr
+ "</div>";
};
this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
}
reference_name(doc, cdt, cdn) {
var d = frappe.get_doc(cdt, cdn);
@@ -404,22 +400,23 @@ frappe.ui.form.on("Journal Entry Account", {
if(!d.account && d.party_type && d.party) {
if(!frm.doc.company) frappe.throw(__("Please select Company"));
return frm.call({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_currency",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance",
child: d,
args: {
company: frm.doc.company,
party_type: d.party_type,
party: d.party,
cost_center: d.cost_center
}
});
}
},
cost_center: function(frm, dt, dn) {
erpnext.journal_entry.set_account_details(frm, dt, dn);
erpnext.journal_entry.set_account_balance(frm, dt, dn);
},
account: function(frm, dt, dn) {
erpnext.journal_entry.set_account_details(frm, dt, dn);
erpnext.journal_entry.set_account_balance(frm, dt, dn);
},
debit_in_account_currency: function(frm, cdt, cdn) {
@@ -603,14 +600,14 @@ $.extend(erpnext.journal_entry, {
});
$.extend(erpnext.journal_entry, {
set_account_details: function(frm, dt, dn) {
set_account_balance: function(frm, dt, dn) {
var d = locals[dt][dn];
if(d.account) {
if(!frm.doc.company) frappe.throw(__("Please select Company first"));
if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first"));
return frappe.call({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_details_and_party_type",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type",
args: {
account: d.account,
date: frm.doc.posting_date,
@@ -618,6 +615,7 @@ $.extend(erpnext.journal_entry, {
debit: flt(d.debit_in_account_currency),
credit: flt(d.credit_in_account_currency),
exchange_rate: d.exchange_rate,
cost_center: d.cost_center
},
callback: function(r) {
if(r.message) {

View File

@@ -64,8 +64,7 @@
"stock_entry",
"subscription_section",
"auto_repeat",
"amended_from",
"repost_required"
"amended_from"
],
"fields": [
{
@@ -544,15 +543,6 @@
"label": "Is System Generated",
"no_copy": 1,
"read_only": 1
},
{
"default": "0",
"fieldname": "repost_required",
"fieldtype": "Check",
"hidden": 1,
"label": "Repost Required",
"print_hide": 1,
"read_only": 1
}
],
"icon": "fa fa-file-text",

View File

@@ -7,16 +7,13 @@ import json
import frappe
from frappe import _, msgprint, scrub
from frappe.utils import cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
from frappe.utils.caching import redis_cache
import erpnext
from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
get_party_account_based_on_invoice_discounting,
)
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
validate_docs_for_voucher_types,
)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
@@ -144,6 +141,7 @@ class JournalEntry(AccountsController):
self.set_print_format_fields()
self.validate_credit_debit_note()
self.validate_empty_accounts_table()
self.set_account_and_party_balance()
self.validate_inter_company_accounts()
self.validate_depr_entry_voucher_type()
@@ -153,10 +151,6 @@ class JournalEntry(AccountsController):
if not self.title:
self.title = self.get_title()
def validate_for_repost(self):
validate_docs_for_voucher_types(["Journal Entry"])
validate_docs_for_deferred_accounting([self.name], [])
def submit(self):
if len(self.accounts) > 100:
msgprint(_("The task has been enqueued as a background job."), alert=True)
@@ -180,15 +174,6 @@ class JournalEntry(AccountsController):
self.update_inter_company_jv()
self.update_invoice_discounting()
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
self.needs_repost = self.check_if_fields_updated(
fields_to_check=[], child_tables={"accounts": []}
)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
def on_cancel(self):
# References for this Journal are removed on the `on_cancel` event in accounts_controller
super(JournalEntry, self).on_cancel()
@@ -575,28 +560,17 @@ class JournalEntry(AccountsController):
elif d.party_type == "Supplier" and flt(d.credit) > 0:
frappe.throw(_("Row {0}: Advance against Supplier must be debit").format(d.idx))
def system_generated_gain_loss(self):
return (
self.voucher_type == "Exchange Gain Or Loss"
and self.multi_currency
and self.is_system_generated
)
def validate_against_jv(self):
for d in self.get("accounts"):
if d.reference_type == "Journal Entry":
account_root_type = frappe.get_cached_value("Account", d.account, "root_type")
if account_root_type == "Asset" and flt(d.debit) > 0 and not self.system_generated_gain_loss():
if account_root_type == "Asset" and flt(d.debit) > 0:
frappe.throw(
_(
"Row #{0}: For {1}, you can select reference document only if account gets credited"
).format(d.idx, d.account)
)
elif (
account_root_type == "Liability"
and flt(d.credit) > 0
and not self.system_generated_gain_loss()
):
elif account_root_type == "Liability" and flt(d.credit) > 0:
frappe.throw(
_(
"Row #{0}: For {1}, you can select reference document only if account gets debited"
@@ -628,7 +602,7 @@ class JournalEntry(AccountsController):
for jvd in against_entries:
if flt(jvd[dr_or_cr]) > 0:
valid = True
if not valid and not self.system_generated_gain_loss():
if not valid:
frappe.throw(
_("Against Journal Entry {0} does not have any unmatched {1} entry").format(
d.reference_name, dr_or_cr
@@ -1176,6 +1150,21 @@ class JournalEntry(AccountsController):
if not self.get("accounts"):
frappe.throw(_("Accounts table cannot be blank."))
def set_account_and_party_balance(self):
account_balance = {}
party_balance = {}
for d in self.get("accounts"):
if d.account not in account_balance:
account_balance[d.account] = get_balance_on(account=d.account, date=self.posting_date)
if (d.party_type, d.party) not in party_balance:
party_balance[(d.party_type, d.party)] = get_balance_on(
party_type=d.party_type, party=d.party, date=self.posting_date, company=self.company
)
d.account_balance = account_balance[d.account]
d.party_balance = party_balance[(d.party_type, d.party)]
@frappe.whitelist()
def get_default_bank_cash_account(company, account_type=None, mode_of_payment=None, account=None):
@@ -1341,6 +1330,8 @@ def get_payment_entry(ref_doc, args):
"account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"),
"account_currency": args.get("party_account_currency")
or get_account_currency(args.get("party_account")),
"balance": get_balance_on(args.get("party_account")),
"party_balance": get_balance_on(party=args.get("party"), party_type=args.get("party_type")),
"exchange_rate": exchange_rate,
args.get("amount_field_party"): args.get("amount"),
"is_advance": args.get("is_advance"),
@@ -1488,23 +1479,31 @@ def get_outstanding(args):
@frappe.whitelist()
def get_party_account_and_currency(company, party_type, party):
@redis_cache(ttl=5 * 60, user=True)
def get_party_account_and_balance(company, party_type, party, cost_center=None):
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
account = get_party_account(party_type, party, company)
account_balance = get_balance_on(account=account, cost_center=cost_center)
party_balance = get_balance_on(
party_type=party_type, party=party, company=company, cost_center=cost_center
)
return {
"account": account,
"balance": account_balance,
"party_balance": party_balance,
"account_currency": frappe.get_cached_value("Account", account, "account_currency"),
}
@frappe.whitelist()
def get_account_details_and_party_type(
account, date, company, debit=None, credit=None, exchange_rate=None
def get_account_balance_and_party_type(
account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None
):
"""Returns dict of account details and party type to be set in Journal Entry on selection of account."""
"""Returns dict of account balance and party type to be set in Journal Entry on selection of account."""
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
@@ -1524,6 +1523,7 @@ def get_account_details_and_party_type(
party_type = ""
grid_values = {
"balance": get_balance_on(account, date, cost_center=cost_center),
"party_type": party_type,
"account_type": account_details.account_type,
"account_currency": account_details.account_currency or company_currency,

View File

@@ -166,37 +166,43 @@ class TestJournalEntry(unittest.TestCase):
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.submit()
self.voucher_no = jv.name
gl_entries = frappe.db.sql(
"""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""",
jv.name,
as_dict=1,
)
self.fields = [
"account",
"account_currency",
"debit",
"debit_in_account_currency",
"credit",
"credit_in_account_currency",
]
self.assertTrue(gl_entries)
self.expected_gle = [
{
"account": "_Test Bank - _TC",
"account_currency": "INR",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 5000,
},
{
"account": "_Test Bank USD - _TC",
expected_values = {
"_Test Bank USD - _TC": {
"account_currency": "USD",
"debit": 5000,
"debit_in_account_currency": 100,
"credit": 0,
"credit_in_account_currency": 0,
},
]
"_Test Bank - _TC": {
"account_currency": "INR",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 5000,
},
}
self.check_gl_entries()
for field in (
"account_currency",
"debit",
"debit_in_account_currency",
"credit",
"credit_in_account_currency",
):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
# cancel
jv.cancel()
@@ -222,37 +228,43 @@ class TestJournalEntry(unittest.TestCase):
rjv.posting_date = nowdate()
rjv.submit()
self.voucher_no = rjv.name
gl_entries = frappe.db.sql(
"""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""",
rjv.name,
as_dict=1,
)
self.fields = [
"account",
"account_currency",
"debit",
"credit",
"debit_in_account_currency",
"credit_in_account_currency",
]
self.assertTrue(gl_entries)
self.expected_gle = [
{
"account": "_Test Bank USD - _TC",
expected_values = {
"_Test Bank USD - _TC": {
"account_currency": "USD",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 100,
},
{
"account": "Sales - _TC",
"Sales - _TC": {
"account_currency": "INR",
"debit": 5000,
"debit_in_account_currency": 5000,
"credit": 0,
"credit_in_account_currency": 0,
},
]
}
self.check_gl_entries()
for field in (
"account_currency",
"debit",
"debit_in_account_currency",
"credit",
"credit_in_account_currency",
):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
def test_disallow_change_in_account_currency_for_a_party(self):
# create jv in USD
@@ -332,25 +344,23 @@ class TestJournalEntry(unittest.TestCase):
jv.insert()
jv.submit()
self.voucher_no = jv.name
expected_values = {
"_Test Cash - _TC": {"cost_center": cost_center},
"_Test Bank - _TC": {"cost_center": cost_center},
}
self.fields = [
"account",
"cost_center",
]
gl_entries = frappe.db.sql(
"""select account, cost_center, debit, credit
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""",
jv.name,
as_dict=1,
)
self.expected_gle = [
{
"account": "_Test Bank - _TC",
"cost_center": cost_center,
},
{
"account": "_Test Cash - _TC",
"cost_center": cost_center,
},
]
self.assertTrue(gl_entries)
self.check_gl_entries()
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_jv_with_project(self):
from erpnext.projects.doctype.project.test_project import make_project
@@ -377,22 +387,23 @@ class TestJournalEntry(unittest.TestCase):
jv.insert()
jv.submit()
self.voucher_no = jv.name
expected_values = {
"_Test Cash - _TC": {"project": project_name},
"_Test Bank - _TC": {"project": project_name},
}
self.fields = ["account", "project"]
gl_entries = frappe.db.sql(
"""select account, project, debit, credit
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""",
jv.name,
as_dict=1,
)
self.expected_gle = [
{
"account": "_Test Bank - _TC",
"project": project_name,
},
{
"account": "_Test Cash - _TC",
"project": project_name,
},
]
self.assertTrue(gl_entries)
self.check_gl_entries()
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project)
def test_jv_account_and_party_balance_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
@@ -415,79 +426,6 @@ class TestJournalEntry(unittest.TestCase):
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
self.assertEqual(expected_account_balance, account_balance)
def test_repost_accounting_entries(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
# Configure Repost Accounting Ledger for JVs
settings = frappe.get_doc("Repost Accounting Ledger Settings")
if not [x for x in settings.allowed_types if x.document_type == "Journal Entry"]:
settings.append("allowed_types", {"document_type": "Journal Entry", "allowed": True})
settings.save()
# Create JV with defaut cost center - _Test Cost Center
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
jv.multi_currency = 0
jv.submit()
# Check GL entries before reposting
self.voucher_no = jv.name
self.fields = [
"account",
"debit_in_account_currency",
"credit_in_account_currency",
"cost_center",
]
self.expected_gle = [
{
"account": "_Test Bank - _TC",
"debit_in_account_currency": 0,
"credit_in_account_currency": 100,
"cost_center": "_Test Cost Center - _TC",
},
{
"account": "_Test Cash - _TC",
"debit_in_account_currency": 100,
"credit_in_account_currency": 0,
"cost_center": "_Test Cost Center - _TC",
},
]
self.check_gl_entries()
# Change cost center for bank account - _Test Cost Center for BS Account
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC"
jv.save()
# Check if repost flag gets set on update after submit
self.assertTrue(jv.repost_required)
jv.repost_accounting_entries()
# Check GL entries after reposting
jv.load_from_db()
self.expected_gle[0]["cost_center"] = "_Test Cost Center for BS Account - _TC"
self.check_gl_entries()
def check_gl_entries(self):
gl = frappe.qb.DocType("GL Entry")
query = frappe.qb.from_(gl)
for field in self.fields:
query = query.select(gl[field])
query = query.where(
(gl.voucher_type == "Journal Entry")
& (gl.voucher_no == self.voucher_no)
& (gl.is_cancelled == 0)
).orderby(gl.account)
gl_entries = query.run(as_dict=True)
for i in range(len(self.expected_gle)):
for field in self.fields:
self.assertEqual(self.expected_gle[i][field], gl_entries[i][field])
def make_journal_entry(
account1,

View File

@@ -9,10 +9,12 @@
"field_order": [
"account",
"account_type",
"balance",
"col_break1",
"bank_account",
"party_type",
"party",
"party_balance",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
@@ -62,7 +64,17 @@
"print_hide": 1
},
{
"allow_on_submit": 1,
"fieldname": "balance",
"fieldtype": "Currency",
"label": "Account Balance",
"no_copy": 1,
"oldfieldname": "balance",
"oldfieldtype": "Data",
"options": "account_currency",
"print_hide": 1,
"read_only": 1
},
{
"default": ":Company",
"description": "If Income or Expense",
"fieldname": "cost_center",
@@ -95,6 +107,14 @@
"label": "Party",
"options": "party_type"
},
{
"fieldname": "party_balance",
"fieldtype": "Currency",
"label": "Party Balance",
"options": "account_currency",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "currency_section",
"fieldtype": "Section Break",
@@ -203,7 +223,6 @@
"no_copy": 1
},
{
"allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
@@ -268,7 +287,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-02-05 01:10:50.224840",
"modified": "2023-11-23 11:44:25.841187",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -731,7 +731,6 @@ class PurchaseInvoice(BuyingController):
"cash_bank_account",
"write_off_account",
"unrealized_profit_loss_account",
"is_opening",
]
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
@@ -1024,14 +1023,9 @@ class PurchaseInvoice(BuyingController):
if provisional_accounting_for_non_stock_items:
if item.purchase_receipt:
provisional_account, pr_qty, pr_base_rate = frappe.get_cached_value(
"Purchase Receipt Item",
item.pr_detail,
["provisional_expense_account", "qty", "base_rate"],
)
provisional_account = provisional_account or self.get_company_default(
"default_provisional_account"
)
provisional_account = frappe.db.get_value(
"Purchase Receipt Item", item.pr_detail, "provisional_expense_account"
) or self.get_company_default("default_provisional_account")
purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
if not purchase_receipt_doc:
@@ -1048,18 +1042,13 @@ class PurchaseInvoice(BuyingController):
"voucher_detail_no": item.pr_detail,
"account": provisional_account,
},
"name",
["name"],
)
if expense_booked_in_pr:
# Intentionally passing purchase invoice item to handle partial billing
purchase_receipt_doc.add_provisional_gl_entry(
item,
gl_entries,
self.posting_date,
provisional_account,
reverse=1,
item_amount=(min(item.qty, pr_qty) * pr_base_rate),
item, gl_entries, self.posting_date, provisional_account, reverse=1
)
if not self.is_internal_transfer():

View File

@@ -1529,7 +1529,18 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
def test_provisional_accounting_entry(self):
setup_provisional_accounting()
create_item("_Test Non Stock Item", is_stock_item=0)
provisional_account = create_account(
account_name="Provision Account",
parent_account="Current Liabilities - _TC",
company="_Test Company",
)
company = frappe.get_doc("Company", "_Test Company")
company.enable_provisional_accounting_for_non_stock_items = 1
company.default_provisional_account = provisional_account
company.save()
pr = make_purchase_receipt(
item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2)
@@ -1573,97 +1584,8 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date
)
toggle_provisional_accounting_setting()
def test_provisional_accounting_entry_for_over_billing(self):
setup_provisional_accounting()
# Configure Buying Settings to allow rate change
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
# Create PR: rate = 1000, qty = 5
pr = make_purchase_receipt(
item_code="_Test Non Stock Item", rate=1000, posting_date=add_days(nowdate(), -2)
)
# Overbill PR: rate = 2000, qty = 10
pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
pi.posting_date = add_days(pr.posting_date, -1)
pi.items[0].qty = 10
pi.items[0].rate = 2000
pi.items[0].expense_account = "Cost of Goods Sold - _TC"
pi.save()
pi.submit()
expected_gle = [
["Cost of Goods Sold - _TC", 20000, 0, add_days(pr.posting_date, -1)],
["Creditors - _TC", 0, 20000, add_days(pr.posting_date, -1)],
]
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
expected_gle_for_purchase_receipt = [
["Provision Account - _TC", 5000, 0, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 5000, pr.posting_date],
["Provision Account - _TC", 0, 5000, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date],
]
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
# Cancel purchase invoice to check reverse provisional entry cancellation
pi.cancel()
expected_gle_for_purchase_receipt_post_pi_cancel = [
["Provision Account - _TC", 0, 5000, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date],
]
check_gl_entries(
self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date
)
toggle_provisional_accounting_setting()
def test_provisional_accounting_entry_for_partial_billing(self):
setup_provisional_accounting()
# Configure Buying Settings to allow rate change
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
# Create PR: rate = 1000, qty = 5
pr = make_purchase_receipt(
item_code="_Test Non Stock Item", rate=1000, posting_date=add_days(nowdate(), -2)
)
# Partially bill PR: rate = 500, qty = 2
pi = create_purchase_invoice_from_receipt(pr.name)
pi.set_posting_time = 1
pi.posting_date = add_days(pr.posting_date, -1)
pi.items[0].qty = 2
pi.items[0].rate = 500
pi.items[0].expense_account = "Cost of Goods Sold - _TC"
pi.save()
pi.submit()
expected_gle = [
["Cost of Goods Sold - _TC", 1000, 0, add_days(pr.posting_date, -1)],
["Creditors - _TC", 0, 1000, add_days(pr.posting_date, -1)],
]
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
expected_gle_for_purchase_receipt = [
["Provision Account - _TC", 5000, 0, pr.posting_date],
["_Test Account Cost for Goods Sold - _TC", 0, 5000, pr.posting_date],
["Provision Account - _TC", 0, 1000, pi.posting_date],
["_Test Account Cost for Goods Sold - _TC", 1000, 0, pi.posting_date],
]
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
toggle_provisional_accounting_setting()
company.enable_provisional_accounting_for_non_stock_items = 0
company.save()
def test_adjust_incoming_rate(self):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
@@ -2342,26 +2264,4 @@ def make_purchase_invoice_against_cost_center(**args):
return pi
def setup_provisional_accounting(**args):
args = frappe._dict(args)
create_item("_Test Non Stock Item", is_stock_item=0)
company = args.company or "_Test Company"
provisional_account = create_account(
account_name=args.account_name or "Provision Account",
parent_account=args.parent_account or "Current Liabilities - _TC",
company=company,
)
toggle_provisional_accounting_setting(
enable=1, company=company, provisional_account=provisional_account
)
def toggle_provisional_accounting_setting(**args):
args = frappe._dict(args)
company = frappe.get_doc("Company", args.company or "_Test Company")
company.enable_provisional_accounting_for_non_stock_items = args.enable or 0
company.default_provisional_account = args.provisional_account
company.save()
test_records = frappe.get_test_records("Purchase Invoice")

View File

@@ -721,7 +721,6 @@ class SalesInvoice(SellingController):
"write_off_account",
"loyalty_redemption_account",
"unrealized_profit_loss_account",
"is_opening",
]
child_tables = {
"items": ("income_account", "expense_account", "discount_account"),

View File

@@ -546,7 +546,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
"GL Entry",
{
"is_cancelled": 0,
"party_type": "Customer",
"party": ["in", parties],
"company": inv.company,
"voucher_no": ["in", vouchers],
@@ -561,7 +560,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
conditions = []
conditions.append(ple.amount.lt(0))
conditions.append(ple.delinked == 0)
conditions.append(ple.party_type == "Customer")
conditions.append(ple.party.isin(parties))
conditions.append(ple.voucher_no == ple.against_voucher_no)
conditions.append(ple.company == inv.company)
@@ -581,7 +579,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
{
"is_cancelled": 0,
"credit": [">", 0],
"party_type": "Customer",
"party": ["in", parties],
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
"company": inv.company,

View File

@@ -63,7 +63,7 @@
},
{
"columns": 1,
"fetch_from": "item_code.stock_uom",
"fetch_from": "stock_item_code.stock_uom",
"fieldname": "uom",
"fieldtype": "Link",
"in_list_view": 1,
@@ -110,7 +110,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-05 11:23:40.766844",
"modified": "2021-09-08 15:52:08.598100",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Service Item",
@@ -118,6 +118,5 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -65,7 +65,7 @@
},
{
"columns": 1,
"fetch_from": "item_code.stock_uom",
"fetch_from": "stock_item_code.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"in_list_view": 1,
@@ -178,7 +178,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-05 11:22:57.346889",
"modified": "2024-02-25 15:57:35.007501",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Stock Item",

View File

@@ -46,7 +46,6 @@ from erpnext.accounts.party import (
from erpnext.accounts.utils import (
create_gain_loss_journal,
get_account_currency,
get_currency_precision,
get_fiscal_years,
validate_fiscal_year,
)
@@ -1300,12 +1299,10 @@ class AccountsController(TransactionBase):
# These are generated by Sales/Purchase Invoice during reconciliation and advance allocation.
# and below logic is only for such scenarios
if args:
precision = get_currency_precision()
for arg in args:
# Advance section uses `exchange_gain_loss` and reconciliation uses `difference_amount`
if (
flt(arg.get("difference_amount", 0), precision) != 0
or flt(arg.get("exchange_gain_loss", 0), precision) != 0
arg.get("difference_amount", 0) != 0 or arg.get("exchange_gain_loss", 0) != 0
) and arg.get("difference_account"):
party_account = arg.get("account")
@@ -2419,20 +2416,27 @@ class AccountsController(TransactionBase):
doc_before_update = self.get_doc_before_save()
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
# Parent Level Accounts excluding party account
fields_to_check += accounting_dimensions
for field in fields_to_check:
if doc_before_update.get(field) != self.get(field):
return True
# Check if opening entry check updated
needs_repost = doc_before_update.get("is_opening") != self.is_opening
# Check for child tables
for table in child_tables:
if check_if_child_table_updated(
doc_before_update.get(table), self.get(table), child_tables[table]
):
return True
if not needs_repost:
# Parent Level Accounts excluding party account
fields_to_check += accounting_dimensions
for field in fields_to_check:
if doc_before_update.get(field) != self.get(field):
needs_repost = 1
break
return False
if not needs_repost:
# Check for child tables
for table in child_tables:
needs_repost = check_if_child_table_updated(
doc_before_update.get(table), self.get(table), child_tables[table]
)
if needs_repost:
break
return needs_repost
@frappe.whitelist()
def repost_accounting_entries(self):
@@ -3458,12 +3462,15 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
def check_if_child_table_updated(
child_table_before_update, child_table_after_update, fields_to_check
):
fields_to_check = list(fields_to_check) + get_accounting_dimensions() + ["cost_center", "project"]
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
# Check if any field affecting accounting entry is altered
for index, item in enumerate(child_table_before_update):
for index, item in enumerate(child_table_after_update):
for field in fields_to_check:
if child_table_after_update[index].get(field) != item.get(field):
if child_table_before_update[index].get(field) != item.get(field):
return True
for dimension in accounting_dimensions:
if child_table_before_update[index].get(dimension) != item.get(dimension):
return True
return False

View File

@@ -56,8 +56,7 @@ class TestAccountsController(FrappeTestCase):
20 series - Sales Invoice against Journals
30 series - Sales Invoice against Credit Notes
40 series - Company default Cost center is unset
50 series = Journals against Journals
90 series - Dimension inheritence
50 series - Dimension inheritence
"""
def setUp(self):
@@ -1205,7 +1204,7 @@ class TestAccountsController(FrappeTestCase):
x.mandatory_for_pl = False
loc.save()
def test_90_dimensions_filter(self):
def test_50_dimensions_filter(self):
"""
Test workings of dimension filters
"""
@@ -1276,7 +1275,7 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 1)
def test_91_cr_note_should_inherit_dimension(self):
def test_51_cr_note_should_inherit_dimension(self):
self.setup_dimensions()
rate_in_account_currency = 1
@@ -1318,7 +1317,7 @@ class TestAccountsController(FrappeTestCase):
frappe.db.get_all("Journal Entry Account", filters={"parent": x.parent}, pluck="department"),
)
def test_92_dimension_inhertiance_exc_gain_loss(self):
def test_52_dimension_inhertiance_exc_gain_loss(self):
# Sales Invoice in Foreign Currency
self.setup_dimensions()
rate = 80
@@ -1356,7 +1355,7 @@ class TestAccountsController(FrappeTestCase):
),
)
def test_93_dimension_inheritance_on_advance(self):
def test_53_dimension_inheritance_on_advance(self):
self.setup_dimensions()
dpt = "Research & Development"
@@ -1401,70 +1400,3 @@ class TestAccountsController(FrappeTestCase):
pluck="department",
),
)
def test_50_journal_against_journal(self):
# Invoice in Foreign Currency
journal_as_invoice = self.create_journal_entry(
acc1=self.debit_usd,
acc1_exc_rate=83,
acc2=self.cash,
acc1_amount=1,
acc2_amount=83,
acc2_exc_rate=1,
)
journal_as_invoice.accounts[0].party_type = "Customer"
journal_as_invoice.accounts[0].party = self.customer
journal_as_invoice = journal_as_invoice.save().submit()
# Payment
journal_as_payment = self.create_journal_entry(
acc1=self.debit_usd,
acc1_exc_rate=75,
acc2=self.cash,
acc1_amount=-1,
acc2_amount=-75,
acc2_exc_rate=1,
)
journal_as_payment.accounts[0].party_type = "Customer"
journal_as_payment.accounts[0].party = self.customer
journal_as_payment = journal_as_payment.save().submit()
# Reconcile the remaining amount
pr = self.create_payment_reconciliation()
# pr.receivable_payable_account = self.debit_usd
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)
# There should be no outstanding in both currencies
journal_as_invoice.reload()
self.assert_ledger_outstanding(journal_as_invoice.doctype, journal_as_invoice.name, 0.0, 0.0)
# Exchange Gain/Loss Journal should've been created.
exc_je_for_si = self.get_journals_for(journal_as_invoice.doctype, journal_as_invoice.name)
exc_je_for_je = self.get_journals_for(journal_as_payment.doctype, journal_as_payment.name)
self.assertNotEqual(exc_je_for_si, [])
self.assertEqual(
len(exc_je_for_si), 2
) # payment also has reference. so, there are 2 journals referencing invoice
self.assertEqual(len(exc_je_for_je), 1)
self.assertIn(exc_je_for_je[0], exc_je_for_si)
# Cancel Payment
journal_as_payment.reload()
journal_as_payment.cancel()
journal_as_invoice.reload()
self.assert_ledger_outstanding(journal_as_invoice.doctype, journal_as_invoice.name, 83.0, 1.0)
# Exchange Gain/Loss Journal should've been cancelled
exc_je_for_si = self.get_journals_for(journal_as_invoice.doctype, journal_as_invoice.name)
exc_je_for_je = self.get_journals_for(journal_as_payment.doctype, journal_as_payment.name)
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_je, [])

View File

@@ -353,7 +353,6 @@ erpnext.patches.v14_0.update_zero_asset_quantity_field
execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
erpnext.patches.v14_0.update_total_asset_cost_field
erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20

View File

@@ -1,14 +0,0 @@
import frappe
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
get_allowed_types_from_settings,
)
def execute():
for dt in get_allowed_types_from_settings():
for dimension in get_accounting_dimensions():
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)

View File

@@ -22,7 +22,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
item_rate = flt(item.rate_with_margin , precision("rate", item));
if (item.discount_percentage && !item.discount_amount) {
if (item.discount_percentage) {
item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100;
}

View File

@@ -13,9 +13,7 @@ class DeprecatedSerialNoValuation:
):
return
serial_nos = self.get_filterd_serial_nos()
if not serial_nos:
return
serial_nos = self.get_serial_nos()
actual_qty = flt(self.sle.actual_qty)
@@ -27,21 +25,8 @@ class DeprecatedSerialNoValuation:
self.stock_value_change += stock_value_change
def get_filterd_serial_nos(self):
serial_nos = []
non_filtered_serial_nos = self.get_serial_nos()
# If the serial no inwarded using the Serial and Batch Bundle, then the serial no should not be considered
for serial_no in non_filtered_serial_nos:
if serial_no and serial_no not in self.serial_no_incoming_rate:
serial_nos.append(serial_no)
return serial_nos
@deprecated
def get_incoming_value_for_serial_nos(self, serial_nos):
from erpnext.stock.utils import get_combine_datetime
# get rate from serial nos within same company
incoming_values = 0.0
for serial_no in serial_nos:
@@ -57,20 +42,18 @@ class DeprecatedSerialNoValuation:
| (table.serial_no.like("%\n" + serial_no + "\n%"))
)
& (table.company == self.sle.company)
& (table.warehouse == self.sle.warehouse)
& (table.serial_and_batch_bundle.isnull())
& (table.actual_qty > 0)
& (table.is_cancelled == 0)
& (
table.posting_datetime <= get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
)
)
.orderby(table.posting_datetime, order=Order.desc)
.limit(1)
).run(as_dict=1)
for sle in stock_ledgers:
self.serial_no_incoming_rate[serial_no] += flt(sle.incoming_rate)
self.serial_no_incoming_rate[serial_no] += (
flt(sle.incoming_rate)
if sle.actual_qty > 0
else (sle.stock_value_difference / sle.actual_qty) * -1
)
incoming_values += self.serial_no_incoming_rate[serial_no]
return incoming_values

View File

@@ -28,6 +28,7 @@
"column_break_18",
"project",
"dimension_col_break",
"custom_dimensions_section",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -926,7 +927,7 @@
"width": "50%"
},
{
"fetch_from": "transporter.supplier_name",
"fetch_from": "transporter.name",
"fieldname": "transporter_name",
"fieldtype": "Data",
"label": "Transporter Name",
@@ -1354,6 +1355,10 @@
"fieldname": "column_break_43",
"fieldtype": "Column Break"
},
{
"fieldname": "custom_dimensions_section",
"fieldtype": "Section Break"
},
{
"fieldname": "shipping_address_section",
"fieldtype": "Section Break",
@@ -1397,7 +1402,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2024-03-05 11:58:47.784349",
"modified": "2023-12-18 17:19:39.368239",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@@ -18,7 +18,8 @@ frappe.listview_settings['Item'] = {
reports: [
{
name: 'Stock Summary',
route: '/app/stock-balance'
report_type: 'Page',
route: 'stock-balance'
},
{
name: 'Stock Ledger',

View File

@@ -415,6 +415,23 @@ class TestLandedCostVoucher(FrappeTestCase):
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=charges)
new_purchase_rate = serial_no_rate + charges
sn_obj = SerialNoValuation(
sle=frappe._dict(
{
"posting_date": today(),
"posting_time": nowtime(),
"item_code": "_Test Serialized Item",
"warehouse": "Stores - TCP1",
"serial_nos": [serial_no],
}
)
)
new_serial_no_rate = sn_obj.get_incoming_rate_of_serial_no(serial_no)
# Since the serial no is already delivered the rate must be zero
self.assertFalse(new_serial_no_rate)
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
filters={

View File

@@ -727,19 +727,16 @@ class PurchaseReceipt(BuyingController):
)
def add_provisional_gl_entry(
self, item, gl_entries, posting_date, provisional_account, reverse=0, item_amount=None
self, item, gl_entries, posting_date, provisional_account, reverse=0
):
credit_currency = get_account_currency(provisional_account)
expense_account = item.expense_account
debit_currency = get_account_currency(item.expense_account)
remarks = self.get("remarks") or _("Accounting Entry for Service")
multiplication_factor = 1
amount = item.base_amount
if reverse:
multiplication_factor = -1
# Post reverse entry for previously posted amount
amount = item_amount
expense_account = frappe.db.get_value(
"Purchase Receipt Item", {"name": item.get("pr_detail")}, ["expense_account"]
)
@@ -749,7 +746,7 @@ class PurchaseReceipt(BuyingController):
account=provisional_account,
cost_center=item.cost_center,
debit=0.0,
credit=multiplication_factor * amount,
credit=multiplication_factor * item.base_amount,
remarks=remarks,
against_account=expense_account,
account_currency=credit_currency,
@@ -763,7 +760,7 @@ class PurchaseReceipt(BuyingController):
gl_entries=gl_entries,
account=expense_account,
cost_center=item.cost_center,
debit=multiplication_factor * amount,
debit=multiplication_factor * item.base_amount,
credit=0.0,
remarks=remarks,
against_account=provisional_account,

View File

@@ -11,7 +11,8 @@ frappe.listview_settings['Putaway Rule'] = {
reports: [
{
name: 'Warehouse Capacity Summary',
route: '/app/warehouse-capacity-summary'
report_type: 'Page',
route: 'warehouse-capacity-summary'
}
]
};

View File

@@ -219,9 +219,14 @@ class RepostItemValuation(Document):
if self.status not in ("Queued", "In Progress"):
return
msg = _("Cannot cancel as processing of cancelled documents is pending.")
msg += "<br>" + _("Please try again in an hour.")
frappe.throw(msg, title=_("Pending processing"))
if not (self.voucher_no and self.voucher_no):
return
transaction_status = frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus")
if transaction_status == 2:
msg = _("Cannot cancel as processing of cancelled documents is pending.")
msg += "<br>" + _("Please try again in an hour.")
frappe.throw(msg, title=_("Pending processing"))
@frappe.whitelist()
def restart_reposting(self):

View File

@@ -332,8 +332,13 @@ class SerialandBatchBundle(Document):
rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field)
for d in self.entries:
if not rate or (
flt(rate, precision) == flt(d.incoming_rate, precision) and d.stock_value_difference
):
continue
d.incoming_rate = flt(rate, precision)
if d.qty:
if self.has_batch_no:
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
if save:

View File

@@ -4,9 +4,8 @@ from typing import List
import frappe
from frappe import _, bold
from frappe.model.naming import make_autoname
from frappe.query_builder.functions import CombineDatetime, Sum, Timestamp
from frappe.query_builder.functions import CombineDatetime, Sum
from frappe.utils import cint, cstr, flt, get_link_to_form, now, nowtime, today
from pypika import Order
from erpnext.stock.deprecated_serial_batch import (
DeprecatedBatchNoValuation,
@@ -425,21 +424,19 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
)
else:
entries = self.get_serial_no_ledgers()
self.serial_no_incoming_rate = defaultdict(float)
self.stock_value_change = 0.0
serial_nos = self.get_serial_nos()
for serial_no in serial_nos:
incoming_rate = self.get_incoming_rate_from_bundle(serial_no)
if not incoming_rate:
continue
self.stock_value_change += incoming_rate
self.serial_no_incoming_rate[serial_no] += incoming_rate
for ledger in entries:
self.stock_value_change += ledger.incoming_rate
self.serial_no_incoming_rate[ledger.serial_no] += ledger.incoming_rate
self.calculate_stock_value_from_deprecarated_ledgers()
def get_incoming_rate_from_bundle(self, serial_no) -> float:
def get_serial_no_ledgers(self):
serial_nos = self.get_serial_nos()
bundle = frappe.qb.DocType("Serial and Batch Bundle")
bundle_child = frappe.qb.DocType("Serial and Batch Entry")
@@ -447,18 +444,20 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
frappe.qb.from_(bundle)
.inner_join(bundle_child)
.on(bundle.name == bundle_child.parent)
.select((bundle_child.incoming_rate * bundle_child.qty).as_("incoming_rate"))
.select(
bundle.name,
bundle_child.serial_no,
(bundle_child.incoming_rate * bundle_child.qty).as_("incoming_rate"),
)
.where(
(bundle.is_cancelled == 0)
& (bundle.docstatus == 1)
& (bundle_child.serial_no == serial_no)
& (bundle.type_of_transaction == "Inward")
& (bundle_child.qty > 0)
& (bundle_child.serial_no.isin(serial_nos))
& (bundle.type_of_transaction.isin(["Inward", "Outward"]))
& (bundle.item_code == self.sle.item_code)
& (bundle_child.warehouse == self.sle.warehouse)
)
.orderby(Timestamp(bundle.posting_date, bundle.posting_time), order=Order.desc)
.limit(1)
.orderby(bundle.posting_date, bundle.posting_time, bundle.creation)
)
# Important to exclude the current voucher to calculate correct the stock value difference
@@ -475,8 +474,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
query = query.where(timestamp_condition)
incoming_rate = query.run()
return flt(incoming_rate[0][0]) if incoming_rate else 0.0
return query.run(as_dict=True)
def get_serial_nos(self):
if self.sle.get("serial_nos"):

View File

@@ -952,12 +952,7 @@ class update_entries_after(object):
get_rate_for_return, # don't move this import to top
)
if (
self.valuation_method == "Moving Average"
and not sle.get("serial_no")
and not sle.get("batch_no")
and not sle.get("serial_and_batch_bundle")
):
if self.valuation_method == "Moving Average":
rate = get_incoming_rate(
{
"item_code": sle.item_code,
@@ -984,18 +979,6 @@ class update_entries_after(object):
voucher_detail_no=sle.voucher_detail_no,
sle=sle,
)
if (
sle.get("serial_and_batch_bundle")
and rate > 0
and sle.voucher_type in ["Delivery Note", "Sales Invoice"]
):
frappe.db.set_value(
sle.voucher_type + " Item",
sle.voucher_detail_no,
"incoming_rate",
rate,
)
elif (
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
and sle.voucher_detail_no