chore: release v13 (#37245)
* feat: provision to make reposting entries from Stock and Account Value Comparison Report (backport #35365) (#37171) * feat: provision to make reposting entries from Stock and Account Value Comparison Report (cherry picked from commit7b818e9d34) * fix: `linter` * fix(ux): throw if no row selected to create repost entries --------- Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com> Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com> * fix: incorrect stock ledger entries in DN (backport #36944) (#37067) * fix: incorrect stock ledger entries in DN (#36944) (cherry picked from commit0e83190c19) # Conflicts: # erpnext/stock/doctype/delivery_note/delivery_note.json * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * feat: `Stock Ledger Variance` report (backport #37165) (#37184) feat: `Stock Ledger Variance` report (#37165) * feat: `Stock Ledger Variance` report * refactor: `get_data()` (cherry picked from commitacda72d616) Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com> * fix: Update `advance_paid` in SO/PO after unlinking from advance entry (cherry picked from commit426350eee6) * test: Impact on SO of advance PE submit and unlinking/replacement by SI (cherry picked from commit8a4954d713) --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com> Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com> Co-authored-by: marination <maricadsouza221197@gmail.com> Co-authored-by: ruthra kumar <ruthra@erpnext.com>
This commit is contained in:
@@ -1783,6 +1783,10 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_outstanding_amount_after_advance_payment_entry_cancellation(self):
|
def test_outstanding_amount_after_advance_payment_entry_cancellation(self):
|
||||||
|
"""Test impact of advance PE submission/cancellation on SI and SO."""
|
||||||
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
|
||||||
|
sales_order = make_sales_order(item_code="138-CMS Shoe", qty=1, price_list_rate=500)
|
||||||
pe = frappe.get_doc(
|
pe = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Payment Entry",
|
"doctype": "Payment Entry",
|
||||||
@@ -1802,10 +1806,25 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
"paid_to": "_Test Cash - _TC",
|
"paid_to": "_Test Cash - _TC",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
pe.append(
|
||||||
|
"references",
|
||||||
|
{
|
||||||
|
"reference_doctype": "Sales Order",
|
||||||
|
"reference_name": sales_order.name,
|
||||||
|
"total_amount": sales_order.grand_total,
|
||||||
|
"outstanding_amount": sales_order.grand_total,
|
||||||
|
"allocated_amount": 300,
|
||||||
|
},
|
||||||
|
)
|
||||||
pe.insert()
|
pe.insert()
|
||||||
pe.submit()
|
pe.submit()
|
||||||
|
|
||||||
|
sales_order.reload()
|
||||||
|
self.assertEqual(sales_order.advance_paid, 300)
|
||||||
|
|
||||||
si = frappe.copy_doc(test_records[0])
|
si = frappe.copy_doc(test_records[0])
|
||||||
|
si.items[0].sales_order = sales_order.name
|
||||||
|
si.items[0].so_detail = sales_order.get("items")[0].name
|
||||||
si.is_pos = 0
|
si.is_pos = 0
|
||||||
si.append(
|
si.append(
|
||||||
"advances",
|
"advances",
|
||||||
@@ -1813,6 +1832,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
"doctype": "Sales Invoice Advance",
|
"doctype": "Sales Invoice Advance",
|
||||||
"reference_type": "Payment Entry",
|
"reference_type": "Payment Entry",
|
||||||
"reference_name": pe.name,
|
"reference_name": pe.name,
|
||||||
|
"reference_row": pe.references[0].name,
|
||||||
"advance_amount": 300,
|
"advance_amount": 300,
|
||||||
"allocated_amount": 300,
|
"allocated_amount": 300,
|
||||||
"remarks": pe.remarks,
|
"remarks": pe.remarks,
|
||||||
@@ -1821,7 +1841,13 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
si.insert()
|
si.insert()
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
si.load_from_db()
|
si.reload()
|
||||||
|
pe.reload()
|
||||||
|
sales_order.reload()
|
||||||
|
|
||||||
|
# Check if SO is unlinked/replaced by SI in PE & if SO advance paid is 0
|
||||||
|
self.assertEqual(pe.references[0].reference_name, si.name)
|
||||||
|
self.assertEqual(sales_order.advance_paid, 0.0)
|
||||||
|
|
||||||
# check outstanding after advance allocation
|
# check outstanding after advance allocation
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -1829,11 +1855,9 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")),
|
flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")),
|
||||||
)
|
)
|
||||||
|
|
||||||
# added to avoid Document has been modified exception
|
|
||||||
pe = frappe.get_doc("Payment Entry", pe.name)
|
|
||||||
pe.cancel()
|
pe.cancel()
|
||||||
|
si.reload()
|
||||||
|
|
||||||
si.load_from_db()
|
|
||||||
# check outstanding after advance cancellation
|
# check outstanding after advance cancellation
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
flt(si.outstanding_amount),
|
flt(si.outstanding_amount),
|
||||||
|
|||||||
@@ -537,6 +537,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
|
|||||||
"""
|
"""
|
||||||
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
|
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
|
||||||
|
|
||||||
|
# Update Advance Paid in SO/PO since they might be getting unlinked
|
||||||
|
if jv_detail.get("reference_type") in ("Sales Order", "Purchase Order"):
|
||||||
|
frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid()
|
||||||
|
|
||||||
if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
|
if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
|
||||||
# adjust the unreconciled balance
|
# adjust the unreconciled balance
|
||||||
amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"])
|
amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"])
|
||||||
@@ -596,6 +600,13 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
|
|||||||
|
|
||||||
if d.voucher_detail_no:
|
if d.voucher_detail_no:
|
||||||
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
|
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
|
||||||
|
|
||||||
|
# Update Advance Paid in SO/PO since they are getting unlinked
|
||||||
|
if existing_row.get("reference_doctype") in ("Sales Order", "Purchase Order"):
|
||||||
|
frappe.get_doc(
|
||||||
|
existing_row.reference_doctype, existing_row.reference_name
|
||||||
|
).set_total_advance_paid()
|
||||||
|
|
||||||
original_row = existing_row.as_dict().copy()
|
original_row = existing_row.as_dict().copy()
|
||||||
existing_row.update(reference_details)
|
existing_row.update(reference_details)
|
||||||
|
|
||||||
|
|||||||
@@ -1268,6 +1268,7 @@
|
|||||||
"depends_on": "eval: doc.is_internal_customer",
|
"depends_on": "eval: doc.is_internal_customer",
|
||||||
"fieldname": "set_target_warehouse",
|
"fieldname": "set_target_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Set Target Warehouse",
|
"label": "Set Target Warehouse",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
@@ -1335,7 +1336,7 @@
|
|||||||
"idx": 146,
|
"idx": 146,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-09-16 17:46:17.701904",
|
"modified": "2023-09-04 14:15:28.363184",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note",
|
"name": "Delivery Note",
|
||||||
|
|||||||
@@ -33,5 +33,43 @@ frappe.query_reports["Stock and Account Value Comparison"] = {
|
|||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.datetime.get_today(),
|
"default": frappe.datetime.get_today(),
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
|
||||||
|
get_datatable_options(options) {
|
||||||
|
return Object.assign(options, {
|
||||||
|
checkboxColumn: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onload(report) {
|
||||||
|
report.page.add_inner_button(__("Create Reposting Entries"), function() {
|
||||||
|
let message = `
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Reposting Entries will change the value of
|
||||||
|
accounts Stock In Hand, and Stock Expenses
|
||||||
|
in the Trial Balance report and will also change
|
||||||
|
the Balance Value in the Stock Balance report.
|
||||||
|
</p>
|
||||||
|
<p>Are you sure you want to create Reposting Entries?</p>
|
||||||
|
</div>`;
|
||||||
|
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
|
||||||
|
let selected_rows = indexes.map(i => frappe.query_report.data[i]);
|
||||||
|
|
||||||
|
if (!selected_rows.length) {
|
||||||
|
frappe.throw(__("Please select rows to create Reposting Entries"));
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.confirm(__(message), () => {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.stock.report.stock_and_account_value_comparison.stock_and_account_value_comparison.create_reposting_entries",
|
||||||
|
args: {
|
||||||
|
rows: selected_rows,
|
||||||
|
company: frappe.query_report.get_filter_values().company
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.utils import get_link_to_form, parse_json
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_currency_precision, get_stock_accounts
|
from erpnext.accounts.utils import get_currency_precision, get_stock_accounts
|
||||||
@@ -134,3 +135,35 @@ def get_columns(filters):
|
|||||||
"width": "120",
|
"width": "120",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_reposting_entries(rows, company):
|
||||||
|
if isinstance(rows, str):
|
||||||
|
rows = parse_json(rows)
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for row in rows:
|
||||||
|
row = frappe._dict(row)
|
||||||
|
|
||||||
|
try:
|
||||||
|
doc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Repost Item Valuation",
|
||||||
|
"based_on": "Transaction",
|
||||||
|
"status": "Queued",
|
||||||
|
"voucher_type": row.voucher_type,
|
||||||
|
"voucher_no": row.voucher_no,
|
||||||
|
"posting_date": row.posting_date,
|
||||||
|
"company": company,
|
||||||
|
"allow_nagative_stock": 1,
|
||||||
|
}
|
||||||
|
).submit()
|
||||||
|
|
||||||
|
entries.append(get_link_to_form("Repost Item Valuation", doc.name))
|
||||||
|
except frappe.DuplicateEntryError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if entries:
|
||||||
|
entries = ", ".join(entries)
|
||||||
|
frappe.msgprint(_("Reposting entries created: {0}").format(entries))
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
const DIFFERENCE_FIELD_NAMES = [
|
||||||
|
"difference_in_qty",
|
||||||
|
"fifo_qty_diff",
|
||||||
|
"fifo_value_diff",
|
||||||
|
"fifo_valuation_diff",
|
||||||
|
"valuation_diff",
|
||||||
|
"fifo_difference_diff",
|
||||||
|
"diff_value_diff"
|
||||||
|
];
|
||||||
|
|
||||||
|
frappe.query_reports["Stock Ledger Variance"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Item",
|
||||||
|
"options": "Item",
|
||||||
|
get_query: function() {
|
||||||
|
return {
|
||||||
|
filters: {is_stock_item: 1, has_serial_no: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Warehouse",
|
||||||
|
"options": "Warehouse",
|
||||||
|
get_query: function() {
|
||||||
|
return {
|
||||||
|
filters: {is_group: 0, disabled: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "difference_in",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Difference In",
|
||||||
|
"options": [
|
||||||
|
"",
|
||||||
|
"Qty",
|
||||||
|
"Value",
|
||||||
|
"Valuation",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "include_disabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Include Disabled",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
formatter (value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
if (DIFFERENCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) {
|
||||||
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
|
get_datatable_options(options) {
|
||||||
|
return Object.assign(options, {
|
||||||
|
checkboxColumn: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onload(report) {
|
||||||
|
report.page.add_inner_button(__('Create Reposting Entries'), () => {
|
||||||
|
let message = `
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Reposting Entries will change the value of
|
||||||
|
accounts Stock In Hand, and Stock Expenses
|
||||||
|
in the Trial Balance report and will also change
|
||||||
|
the Balance Value in the Stock Balance report.
|
||||||
|
</p>
|
||||||
|
<p>Are you sure you want to create Reposting Entries?</p>
|
||||||
|
</div>`;
|
||||||
|
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
|
||||||
|
let selected_rows = indexes.map(i => frappe.query_report.data[i]);
|
||||||
|
|
||||||
|
if (!selected_rows.length) {
|
||||||
|
frappe.throw(__("Please select rows to create Reposting Entries"));
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.confirm(__(message), () => {
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.stock.report.stock_ledger_invariant_check.stock_ledger_invariant_check.create_reposting_entries',
|
||||||
|
args: {
|
||||||
|
rows: selected_rows,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2023-09-20 10:44:19.414449",
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"letterhead": null,
|
||||||
|
"modified": "2023-09-20 10:44:19.414449",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
|
"name": "Stock Ledger Variance",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Stock Ledger Entry",
|
||||||
|
"report_name": "Stock Ledger Variance",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import cint, flt
|
||||||
|
|
||||||
|
from erpnext.stock.report.stock_ledger_invariant_check.stock_ledger_invariant_check import (
|
||||||
|
get_data as stock_ledger_invariant_check,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
columns, data = [], []
|
||||||
|
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
columns = get_columns()
|
||||||
|
data = get_data(filters)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"fieldname": "name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": _("Stock Ledger Entry"),
|
||||||
|
"options": "Stock Ledger Entry",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": _("Posting Date"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_time",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": _("Posting Time"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "creation",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": _("Creation"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": _("Item"),
|
||||||
|
"options": "Item",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": _("Warehouse"),
|
||||||
|
"options": "Warehouse",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": _("Voucher Type"),
|
||||||
|
"options": "DocType",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": _("Voucher No"),
|
||||||
|
"options": "voucher_type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_no",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": _("Batch"),
|
||||||
|
"options": "Batch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "use_batchwise_valuation",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": _("Batchwise Valuation"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "actual_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("Qty Change"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "incoming_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("Incoming Rate"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "consumption_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("Consumption Rate"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qty_after_transaction",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(A) Qty After Transaction"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "expected_qty_after_transaction",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(B) Expected Qty After Transaction"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "difference_in_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("A - B"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_queue",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": _("FIFO/LIFO Queue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_queue_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(C) Total Qty in Queue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_qty_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("A - C"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_value",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(D) Balance Stock Value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_stock_value",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(E) Balance Stock Value in Queue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_value_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("D - E"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_value_difference",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(F) Change in Stock Value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_value_from_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(G) Sum of Change in Stock Value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "diff_value_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("G - D"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_stock_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(H) Change in Stock Value (FIFO Queue)"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_difference_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("H - F"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "valuation_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(I) Valuation Rate"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_valuation_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(J) Valuation Rate as per FIFO"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_valuation_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("I - J"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "balance_value_by_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(K) Valuation = Value (D) ÷ Qty (A)"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "valuation_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("I - K"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(filters=None):
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
item_warehouse_map = get_item_warehouse_combinations(filters)
|
||||||
|
|
||||||
|
data = []
|
||||||
|
if item_warehouse_map:
|
||||||
|
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
|
||||||
|
|
||||||
|
for item_warehouse in item_warehouse_map:
|
||||||
|
report_data = stock_ledger_invariant_check(item_warehouse)
|
||||||
|
|
||||||
|
if not report_data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for row in report_data:
|
||||||
|
if has_difference(row, precision, filters.difference_in):
|
||||||
|
data.append(add_item_warehouse_details(row, item_warehouse))
|
||||||
|
break
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_item_warehouse_combinations(filters: dict = None) -> dict:
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
|
||||||
|
bin = frappe.qb.DocType("Bin")
|
||||||
|
item = frappe.qb.DocType("Item")
|
||||||
|
warehouse = frappe.qb.DocType("Warehouse")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(bin)
|
||||||
|
.inner_join(item)
|
||||||
|
.on(bin.item_code == item.name)
|
||||||
|
.inner_join(warehouse)
|
||||||
|
.on(bin.warehouse == warehouse.name)
|
||||||
|
.select(
|
||||||
|
bin.item_code,
|
||||||
|
bin.warehouse,
|
||||||
|
)
|
||||||
|
.where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
if filters.item_code:
|
||||||
|
query = query.where(item.name == filters.item_code)
|
||||||
|
if filters.warehouse:
|
||||||
|
query = query.where(warehouse.name == filters.warehouse)
|
||||||
|
if not filters.include_disabled:
|
||||||
|
query = query.where((item.disabled == 0) & (warehouse.disabled == 0))
|
||||||
|
|
||||||
|
return query.run(as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
|
def has_difference(row, precision, difference_in):
|
||||||
|
has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
|
||||||
|
has_value_difference = (
|
||||||
|
flt(row.diff_value_diff, precision)
|
||||||
|
or flt(row.fifo_value_diff, precision)
|
||||||
|
or flt(row.fifo_difference_diff, precision)
|
||||||
|
)
|
||||||
|
has_valuation_difference = flt(row.valuation_diff, precision) or flt(
|
||||||
|
row.fifo_valuation_diff, precision
|
||||||
|
)
|
||||||
|
|
||||||
|
if difference_in == "Qty" and has_qty_difference:
|
||||||
|
return True
|
||||||
|
elif difference_in == "Value" and has_value_difference:
|
||||||
|
return True
|
||||||
|
elif difference_in == "Valuation" and has_valuation_difference:
|
||||||
|
return True
|
||||||
|
elif difference_in not in ["Qty", "Value", "Valuation"] and (
|
||||||
|
has_qty_difference or has_value_difference or has_valuation_difference
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def add_item_warehouse_details(row, item_warehouse):
|
||||||
|
row.update(
|
||||||
|
{
|
||||||
|
"item_code": item_warehouse.item_code,
|
||||||
|
"warehouse": item_warehouse.warehouse,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return row
|
||||||
Reference in New Issue
Block a user