from __future__ import annotations import tempfile import unittest from datetime import date, timedelta from decimal import Decimal from pathlib import Path from openpyxl import load_workbook from monthly_reports.contracts import ( ChannelObservation, DailyVersionPin, MonthlyFact, MonthlySnapshot, STANDARD_SHEETS, ) from monthly_reports.core import build_monthly_report from monthly_reports.publishing import OpenpyxlWorkbookBuilder def report_fixture(): source = MonthlyFact( daily_record_id=1, daily_version_id=100, business_date=date(2026, 7, 8), channel_key="QBD", arrival=date(2026, 7, 8), departure=date(2026, 7, 10), nights=2, adults=2, children=0, block_code="=SYN-FORMULA-LIKE", no_of_rooms=1, company_name="SYN-COMPANY", confirmation_no="SYN-CONFIRMATION", disp_room_no="SYN-ROOM", effective_rate_amount=Decimal("450.50"), full_name="SYN-GUEST", res_comment="SYN-COMMENT", trace_text="", products="", rate_code="SYN-RATE", room_category_label="SYN-TYPE", real_price=Decimal("450.50"), total_price=Decimal("901.00"), kb_amount=None, ) return build_monthly_report( 2026, 7, date(2026, 7, 8), MonthlySnapshot( (source,), (DailyVersionPin(date(2026, 7, 8), 100),), (ChannelObservation(date(2026, 7, 8), 100, "QBD", 1, 1),), ), ) 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() with tempfile.TemporaryDirectory(prefix="monthly-report-xlsx-") as temp_dir: built = OpenpyxlWorkbookBuilder().build(report, Path(temp_dir)) self.assertTrue(built.path.is_file()) self.assertEqual(built.path.stat().st_mode & 0o777, 0o600) self.assertEqual(built.summary["sheet_names"][:5], [ "LIANTAI-GROUP", "LIANTAI-FIT", "QBD", "DY-AI-Easy-KB", "FENGRUN" ]) self.assertEqual(built.summary["row_counts"], [0, 0, 1, 0, 0]) self.assertEqual(built.summary["formula_count"], 1) self.assertEqual(built.summary["preview_count"], 5) self.assertEqual(len(built.summary["semantic_sha256"]), 64) workbook = load_workbook(built.path, data_only=False, read_only=True) try: self.assertEqual(workbook["QBD"]["S2"].value, "=R2*C2*G2") self.assertEqual(workbook["QBD"]["F2"].value, "'=SYN-FORMULA-LIKE") formulas = [ cell.value for sheet in workbook.worksheets for row in sheet.iter_rows() for cell in row if cell.data_type == "f" ] self.assertEqual(formulas, ["=R2*C2*G2"]) 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()