From 6ab0046e9c6d311c5496347a37770f30ffd59f52 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 10 May 2022 12:32:56 +0530 Subject: [PATCH] fix: consider previous balance is missing Also remove `total`, total of total is a meaningless value. --- .../report/stock_analytics/stock_analytics.py | 52 ++++++++++++++++--- .../stock_analytics/test_stock_analytics.py | 12 +++++ 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py index da0776b9a8..77faa1f679 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/stock_analytics.py @@ -1,6 +1,7 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt import datetime +from typing import List import frappe from frappe import _, scrub @@ -148,11 +149,19 @@ def get_periodic_data(entry, filters): - Warehouse A : bal_qty/value - Warehouse B : bal_qty/value """ + + expected_ranges = get_period_date_ranges(filters) + expected_periods = [] + for _start_date, end_date in expected_ranges: + expected_periods.append(get_period(end_date, filters)) + periodic_data = {} for d in entry: period = get_period(d.posting_date, filters) bal_qty = 0 + fill_intermediate_periods(periodic_data, d.item_code, period, expected_periods) + # if period against item does not exist yet, instantiate it # insert existing balance dict against period, and add/subtract to it if periodic_data.get(d.item_code) and not periodic_data.get(d.item_code).get(period): @@ -186,6 +195,36 @@ def get_periodic_data(entry, filters): return periodic_data +def fill_intermediate_periods( + periodic_data, item_code: str, current_period: str, all_periods: List[str] +) -> None: + """There might be intermediate periods where no stock ledger entry exists, copy previous previous data. + + Previous data is ONLY copied if period falls in report range and before period being processed currently. + + args: + current_period: process till this period (exclusive) + all_periods: all periods expected in report via filters + periodic_data: report's periodic data + item_code: item_code being processed + """ + + previous_period_data = None + for period in all_periods: + if period == current_period: + return + + if ( + periodic_data.get(item_code) + and not periodic_data.get(item_code).get(period) + and previous_period_data + ): + # This period should exist since it's in report range, assign previous period data + periodic_data[item_code][period] = previous_period_data.copy() + + previous_period_data = periodic_data.get(item_code, {}).get(period) + + def get_data(filters): data = [] items = get_items(filters) @@ -202,14 +241,15 @@ def get_data(filters): "uom": item_data.stock_uom, "brand": item_data.brand, } - total = 0 - for dummy, end_date in ranges: + previous_period_value = 0.0 + for _start_date, end_date in ranges: period = get_period(end_date, filters) period_data = periodic_data.get(item_data.name, {}).get(period) - amount = sum(period_data.values()) if period_data else 0 - row[scrub(period)] = amount - total += amount - row["total"] = total + if period_data: + row[scrub(period)] = previous_period_value = sum(period_data.values()) + else: + row[scrub(period)] = previous_period_value + data.append(row) return data diff --git a/erpnext/stock/report/stock_analytics/test_stock_analytics.py b/erpnext/stock/report/stock_analytics/test_stock_analytics.py index d9f10e5455..dd8f8d8038 100644 --- a/erpnext/stock/report/stock_analytics/test_stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/test_stock_analytics.py @@ -102,3 +102,15 @@ class TestStockAnalyticsReport(FrappeTestCase): (20, add_to_date(today, months=3).replace(day=15)), ] self.assert_single_item_report(movement, [100, 50, 50, 70]) + + def test_multi_month_missings(self): + today = getdate() + movement = [ + (100, add_to_date(today, months=0).replace(day=15)), + (-50, add_to_date(today, months=1).replace(day=15)), + # Skip a month + (20, add_to_date(today, months=3).replace(day=15)), + # Skip another month + (-10, add_to_date(today, months=5).replace(day=15)), + ] + self.assert_single_item_report(movement, [100, 50, 50, 70, 70, 60])