fix: tolerate spreadsheet decimal roundoff
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from datetime import date
|
||||
from datetime import date, timedelta
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
@@ -13,6 +13,7 @@ from monthly_reports.contracts import (
|
||||
DailyVersionPin,
|
||||
MonthlyFact,
|
||||
MonthlySnapshot,
|
||||
STANDARD_SHEETS,
|
||||
)
|
||||
from monthly_reports.core import build_monthly_report
|
||||
from monthly_reports.publishing import OpenpyxlWorkbookBuilder
|
||||
@@ -57,6 +58,77 @@ def report_fixture():
|
||||
)
|
||||
|
||||
|
||||
def large_decimal_report_fixture():
|
||||
facts = []
|
||||
daily_versions = []
|
||||
channel_observations = []
|
||||
record_id = 1000
|
||||
daily_plan = (
|
||||
(date(2026, 8, 1), 501, 28),
|
||||
(date(2026, 8, 2), 502, 30),
|
||||
(date(2026, 8, 3), 503, 30),
|
||||
)
|
||||
for business_date, daily_version_id, rows_per_sheet in daily_plan:
|
||||
daily_versions.append(DailyVersionPin(business_date, daily_version_id))
|
||||
for channel_index, channel_key in enumerate(STANDARD_SHEETS, start=1):
|
||||
channel_observations.append(
|
||||
ChannelObservation(
|
||||
business_date,
|
||||
daily_version_id,
|
||||
channel_key,
|
||||
channel_index,
|
||||
rows_per_sheet,
|
||||
)
|
||||
)
|
||||
for row_index in range(rows_per_sheet):
|
||||
nights = 1 + (row_index % 3)
|
||||
no_of_rooms = 1 + (row_index % 2)
|
||||
real_price = Decimal("321.17") + Decimal(row_index % 19) / Decimal("100")
|
||||
sequence = record_id
|
||||
facts.append(
|
||||
MonthlyFact(
|
||||
daily_record_id=sequence,
|
||||
daily_version_id=daily_version_id,
|
||||
business_date=business_date,
|
||||
channel_key=channel_key,
|
||||
arrival=business_date,
|
||||
departure=business_date + timedelta(days=nights),
|
||||
nights=nights,
|
||||
adults=2,
|
||||
children=row_index % 2,
|
||||
block_code=f"BLOCK-{sequence}",
|
||||
no_of_rooms=no_of_rooms,
|
||||
company_name=f"COMPANY-{channel_index}",
|
||||
confirmation_no=f"CONF-{sequence}",
|
||||
disp_room_no=f"ROOM-{sequence}",
|
||||
effective_rate_amount=Decimal("247.35")
|
||||
+ Decimal((row_index + channel_index) % 7) / Decimal("100"),
|
||||
full_name=f"GUEST-{sequence}",
|
||||
res_comment=f"COMMENT-{sequence}",
|
||||
trace_text="TRACE",
|
||||
products="ROOM",
|
||||
rate_code="BAR",
|
||||
room_category_label="STD",
|
||||
real_price=real_price,
|
||||
total_price=real_price * no_of_rooms * nights,
|
||||
kb_amount=Decimal(no_of_rooms * 100)
|
||||
if channel_key == "DY-AI-Easy-KB"
|
||||
else None,
|
||||
)
|
||||
)
|
||||
record_id += 1
|
||||
return build_monthly_report(
|
||||
2026,
|
||||
8,
|
||||
date(2026, 8, 3),
|
||||
MonthlySnapshot(
|
||||
tuple(facts),
|
||||
tuple(daily_versions),
|
||||
tuple(channel_observations),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class MonthlyReportsXlsxTests(unittest.TestCase):
|
||||
def test_openpyxl_builder_reopens_checks_formulas_and_semantic_identity(self):
|
||||
report = report_fixture()
|
||||
@@ -88,6 +160,31 @@ class MonthlyReportsXlsxTests(unittest.TestCase):
|
||||
finally:
|
||||
workbook.close()
|
||||
|
||||
def test_openpyxl_builder_validates_440_decimal_rows_across_multiple_sheets(self):
|
||||
report = large_decimal_report_fixture()
|
||||
self.assertEqual(report.row_count, 440)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="monthly-report-xlsx-large-") as temp_dir:
|
||||
built = OpenpyxlWorkbookBuilder().build(report, Path(temp_dir))
|
||||
|
||||
self.assertEqual(built.summary["sheet_names"], list(STANDARD_SHEETS))
|
||||
self.assertEqual(built.summary["row_counts"], [88, 88, 88, 88, 88])
|
||||
self.assertEqual(built.summary["formula_count"], 440)
|
||||
self.assertTrue(
|
||||
OpenpyxlWorkbookBuilder._decimal_matches("321.1700005", "321.17")
|
||||
)
|
||||
self.assertFalse(
|
||||
OpenpyxlWorkbookBuilder._decimal_matches("321.1700011", "321.17")
|
||||
)
|
||||
self.assertEqual(
|
||||
OpenpyxlWorkbookBuilder._validate_workbook(
|
||||
built.path,
|
||||
report,
|
||||
report.to_workbook_payload(),
|
||||
),
|
||||
440,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user