|
8 | 8 | DependencyResolver, |
9 | 9 | FilterExpressionParser, |
10 | 10 | FinancialQueryBuilder, |
| 11 | + FinancialReportEngine, |
11 | 12 | FormulaCalculator, |
12 | 13 | ) |
13 | 14 | from erpnext.accounts.doctype.financial_report_template.test_financial_report_template import ( |
@@ -2022,3 +2023,210 @@ def test_account_with_gl_entries_but_no_prior_closing_balance(self): |
2022 | 2023 |
|
2023 | 2024 | finally: |
2024 | 2025 | jv.cancel() |
| 2026 | + |
| 2027 | + def test_pl_pcv_exclusion_and_growth_view_year_over_year(self): |
| 2028 | + """ |
| 2029 | + Sequence: |
| 2030 | + 1. Expense JV 2000 in FY 2024, PCV for FY 2024 |
| 2031 | + → assert FY 2024 movement = 2000 via FinancialQueryBuilder |
| 2032 | + 2. Expense JV 3000 in FY 2025, PCV for FY 2025 |
| 2033 | + 3. Run FinancialReportEngine with selected_view="Growth" |
| 2034 | + → assert col_2024 = 2000 (raw), col_2025 = 50.0 (% growth) |
| 2035 | + """ |
| 2036 | + company = "_Test Company" |
| 2037 | + expense_account = "Administrative Expenses - _TC" |
| 2038 | + bank_account = "_Test Bank - _TC" |
| 2039 | + |
| 2040 | + template = None |
| 2041 | + pcv_2024 = None |
| 2042 | + pcv_2025 = None |
| 2043 | + jv_2024 = None |
| 2044 | + jv_2025 = None |
| 2045 | + original_pcv_setting = frappe.db.get_single_value( |
| 2046 | + "Accounts Settings", "use_legacy_controller_for_pcv" |
| 2047 | + ) |
| 2048 | + |
| 2049 | + try: |
| 2050 | + closing_account = frappe.db.get_value( |
| 2051 | + "Account", |
| 2052 | + { |
| 2053 | + "company": company, |
| 2054 | + "root_type": "Liability", |
| 2055 | + "is_group": 0, |
| 2056 | + "account_type": ["not in", ["Payable", "Receivable"]], |
| 2057 | + }, |
| 2058 | + "name", |
| 2059 | + ) |
| 2060 | + |
| 2061 | + frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1) |
| 2062 | + |
| 2063 | + accounts = [ |
| 2064 | + frappe._dict( |
| 2065 | + { |
| 2066 | + "name": expense_account, |
| 2067 | + "account_name": "Administrative Expenses", |
| 2068 | + "account_number": "5001", |
| 2069 | + } |
| 2070 | + ), |
| 2071 | + ] |
| 2072 | + |
| 2073 | + # --- Step 1: FY 2024 expense + PCV, assert PCV reversal excluded --- |
| 2074 | + jv_2024 = make_journal_entry( |
| 2075 | + account1=expense_account, |
| 2076 | + account2=bank_account, |
| 2077 | + amount=2000, |
| 2078 | + posting_date="2024-06-15", |
| 2079 | + company=company, |
| 2080 | + submit=True, |
| 2081 | + ) |
| 2082 | + fy_2024 = get_fiscal_year("2024-06-15", company=company) |
| 2083 | + pcv_2024 = frappe.get_doc( |
| 2084 | + { |
| 2085 | + "doctype": "Period Closing Voucher", |
| 2086 | + "transaction_date": "2024-12-31", |
| 2087 | + "period_start_date": fy_2024[1], |
| 2088 | + "period_end_date": fy_2024[2], |
| 2089 | + "company": company, |
| 2090 | + "fiscal_year": fy_2024[0], |
| 2091 | + "cost_center": "_Test Cost Center - _TC", |
| 2092 | + "closing_account_head": closing_account, |
| 2093 | + "remarks": "Test PCV FY 2024", |
| 2094 | + } |
| 2095 | + ) |
| 2096 | + pcv_2024.insert() |
| 2097 | + pcv_2024.submit() |
| 2098 | + pcv_2024.reload() |
| 2099 | + |
| 2100 | + builder_2024 = FinancialQueryBuilder( |
| 2101 | + { |
| 2102 | + "company": company, |
| 2103 | + "from_fiscal_year": "2024", |
| 2104 | + "to_fiscal_year": "2024", |
| 2105 | + "period_start_date": "2024-01-01", |
| 2106 | + "period_end_date": "2024-12-31", |
| 2107 | + "filter_based_on": "Date Range", |
| 2108 | + "periodicity": "Yearly", |
| 2109 | + }, |
| 2110 | + [{"key": "2024", "from_date": "2024-01-01", "to_date": "2024-12-31"}], |
| 2111 | + ) |
| 2112 | + data_2024 = builder_2024.fetch_account_balances(accounts) |
| 2113 | + expense_2024 = data_2024.get(expense_account) |
| 2114 | + self.assertIsNotNone(expense_2024, "Expense account must appear in FY 2024 results") |
| 2115 | + year_2024 = expense_2024.get_period("2024") |
| 2116 | + self.assertEqual( |
| 2117 | + year_2024.movement, |
| 2118 | + 2000.0, |
| 2119 | + "FY 2024 expense movement must equal real expense (PCV reversal excluded)", |
| 2120 | + ) |
| 2121 | + |
| 2122 | + # --- Step 2: FY 2025 expense + PCV --- |
| 2123 | + jv_2025 = make_journal_entry( |
| 2124 | + account1=expense_account, |
| 2125 | + account2=bank_account, |
| 2126 | + amount=3000, |
| 2127 | + posting_date="2025-06-15", |
| 2128 | + company=company, |
| 2129 | + submit=True, |
| 2130 | + ) |
| 2131 | + fy_2025 = get_fiscal_year("2025-06-15", company=company) |
| 2132 | + pcv_2025 = frappe.get_doc( |
| 2133 | + { |
| 2134 | + "doctype": "Period Closing Voucher", |
| 2135 | + "transaction_date": "2025-12-31", |
| 2136 | + "period_start_date": fy_2025[1], |
| 2137 | + "period_end_date": fy_2025[2], |
| 2138 | + "company": company, |
| 2139 | + "fiscal_year": fy_2025[0], |
| 2140 | + "cost_center": "_Test Cost Center - _TC", |
| 2141 | + "closing_account_head": closing_account, |
| 2142 | + "remarks": "Test PCV FY 2025", |
| 2143 | + } |
| 2144 | + ) |
| 2145 | + pcv_2025.insert() |
| 2146 | + pcv_2025.submit() |
| 2147 | + pcv_2025.reload() |
| 2148 | + |
| 2149 | + # --- Step 3: full pipeline with Growth view across both years --- |
| 2150 | + template_name = f"Test Growth Template {frappe.generate_hash()[:8]}" |
| 2151 | + template = frappe.get_doc( |
| 2152 | + { |
| 2153 | + "doctype": "Financial Report Template", |
| 2154 | + "template_name": template_name, |
| 2155 | + "report_type": "Profit and Loss Statement", |
| 2156 | + "rows": [ |
| 2157 | + { |
| 2158 | + "reference_code": "EXP_ADMIN", |
| 2159 | + "display_name": "Administrative Expenses", |
| 2160 | + "indentation_level": 0, |
| 2161 | + "data_source": "Account Data", |
| 2162 | + "balance_type": "Closing Balance", |
| 2163 | + "calculation_formula": f'["name", "=", "{expense_account}"]', |
| 2164 | + }, |
| 2165 | + ], |
| 2166 | + } |
| 2167 | + ) |
| 2168 | + template.insert() |
| 2169 | + |
| 2170 | + filters = frappe._dict( |
| 2171 | + { |
| 2172 | + "company": company, |
| 2173 | + "report_template": template_name, |
| 2174 | + "from_fiscal_year": fy_2024[0], |
| 2175 | + "to_fiscal_year": fy_2025[0], |
| 2176 | + "period_start_date": "2024-01-01", |
| 2177 | + "period_end_date": "2025-12-31", |
| 2178 | + "filter_based_on": "Date Range", |
| 2179 | + "periodicity": "Yearly", |
| 2180 | + "accumulated_values": 0, |
| 2181 | + "selected_view": "Growth", |
| 2182 | + } |
| 2183 | + ) |
| 2184 | + |
| 2185 | + _columns, formatted_data, _msg, _chart = FinancialReportEngine().execute(filters) |
| 2186 | + |
| 2187 | + expense_row = next( |
| 2188 | + (row for row in formatted_data if row.get("account_name") == "Administrative Expenses"), |
| 2189 | + None, |
| 2190 | + ) |
| 2191 | + self.assertIsNotNone(expense_row, "Administrative Expenses row must appear in growth view") |
| 2192 | + |
| 2193 | + period_keys = expense_row.get("_segment_info", {}).get("period_keys", []) |
| 2194 | + self.assertEqual(len(period_keys), 2, "Yearly view must yield exactly two periods") |
| 2195 | + first_period_key, second_period_key = period_keys |
| 2196 | + |
| 2197 | + # First column: raw absolute value (FY 2024 expense) |
| 2198 | + self.assertEqual( |
| 2199 | + flt(expense_row[first_period_key]), |
| 2200 | + 2000.0, |
| 2201 | + "First column in growth view must keep raw FY 2024 expense value", |
| 2202 | + ) |
| 2203 | + # Second column: ((3000 - 2000) / 2000) * 100 = 50.0 |
| 2204 | + self.assertEqual( |
| 2205 | + flt(expense_row[second_period_key]), |
| 2206 | + 50.0, |
| 2207 | + "Second column must be % growth FY 2024 → FY 2025", |
| 2208 | + ) |
| 2209 | + |
| 2210 | + finally: |
| 2211 | + frappe.db.set_single_value( |
| 2212 | + "Accounts Settings", "use_legacy_controller_for_pcv", original_pcv_setting or 0 |
| 2213 | + ) |
| 2214 | + |
| 2215 | + if pcv_2025: |
| 2216 | + pcv_2025.reload() |
| 2217 | + if pcv_2025.docstatus == 1: |
| 2218 | + pcv_2025.cancel() |
| 2219 | + |
| 2220 | + if jv_2025 and jv_2025.docstatus == 1: |
| 2221 | + jv_2025.cancel() |
| 2222 | + |
| 2223 | + if pcv_2024: |
| 2224 | + pcv_2024.reload() |
| 2225 | + if pcv_2024.docstatus == 1: |
| 2226 | + pcv_2024.cancel() |
| 2227 | + |
| 2228 | + if jv_2024 and jv_2024.docstatus == 1: |
| 2229 | + jv_2024.cancel() |
| 2230 | + |
| 2231 | + if template and frappe.db.exists("Financial Report Template", template.name): |
| 2232 | + frappe.delete_doc("Financial Report Template", template.name, force=1) |
0 commit comments