"""Отображаемое значение числа по числовому формату ячейки (#42, R-2…R-6): примеры из реальных выгрузок 1С.""" from decimal import Decimal import pytest from src.parser.statements.number_format import FormatResult, display_amount def shown(raw: str, fmt: str) -> tuple[str, Decimal | None]: result = display_amount(Decimal(raw), fmt) return result.kind, result.value @pytest.mark.parametrize( ("raw", "fmt", "expected"), [ ("10741981390", "#,##0,", "10741981.00"), ("586260.26", "0,", "586.00"), ("898852.21795", "#,##0", "898852.00"), ("898852.5", "#,##0", "898853.00"), ("1234.5", "#,##0.00", "1234.50"), ("1234.567", "#,##0.00", "1234.57"), ("1500", "#,##0,", "2.00"), ("-1500", "#,##0,", "-2.00"), ("1499.99", "#,##0,", "1.00"), ("-1499.99", "#,##0,", "-1.00"), ("586260.26", "0.00,", "586.26"), ("5000000", "#,##0,,", "5.00"), ("1500", '#,##0,;(#,##0,);"-"', "2.00"), ("-1500", '#,##0,;(#,##0,);"-"', "-2.00"), ("1500", "#,##0;(#,##0)", "1500.00"), ("-1500", "#,##0;(#,##0)", "-1500.00"), ("-1500", "#,##0;#,##0", "-1500.00"), ("1500", "[Red]#,##0", "1500.00"), ("1500", "[$-419]#,##0", "1500.00"), ("1500", "#,##0_);[Red](#,##0)", "1500.00"), ("-1500", "#,##0_);[Red](#,##0)", "-1500.00"), ("1500.126", "0.000", "1500.13"), ("1500", '0 "тыс."', "1500.00"), ("-872206880.07", '[=-872206880.07]"(872 207)";#,##0,', "-872207.00"), ("-872206881", '[=-872206880.07]"(872 207)";#,##0,', "-872207.00"), ("-646532", '[=-646532]"(647)"', "-647.00"), ("12345678", '[=-872206880.07]"(872 207)";#,##0,', "12346.00"), ("1500499.9999999998", "#,##0,", "1501.00"), ("1500", "[$-419]#,##0,", "2.00"), ("1500", "\\(#,##0\\)", "1500.00"), ("5", "[=5]0;[=5]0,", "5.00"), ("-1500", "[$-419]#,##0,", "-2.00"), ], ) def test_value(raw: str, fmt: str, expected: str) -> None: kind, value = shown(raw, fmt) assert kind == "value" assert value is not None assert str(value) == expected @pytest.mark.parametrize( ("raw", "fmt"), [ ("0", '[=0]"-"'), ("0", '[=0]""'), ("0", '[=0]"-";General'), ("-0.79", '[=-0.79]"-"'), ("0", '#,##0,;(#,##0,);"-"'), ("0", ";;;"), ("7", ";;;"), ], ) def test_no_value(raw: str, fmt: str) -> None: assert shown(raw, fmt) == ("value", None) @pytest.mark.parametrize( ("raw", "fmt"), [ ("5", '[=0]"-"'), ("5", "0%"), ("5", "0.00E+00"), ("5", "# ?/?"), ("5", "@"), ("5", "dd.mm.yyyy"), ("5", "h:mm AM/PM"), ("0", '[=0]"н/д"'), ("5", '"н/д"'), ("5", "0 руб"), ("5", "[=1E-5]0"), ("5", "[=1,5]0"), ("5", "0 ₽"), ("5", "0;0;0;@;0"), ], ) def test_unparsed(raw: str, fmt: str) -> None: assert shown(raw, fmt) == ("unparsed", None) @pytest.mark.parametrize("fmt", ["General", "general", "", " "]) def test_raw(fmt: str) -> None: assert shown("5.5", fmt) == ("raw", None) def test_general_section_after_condition() -> None: assert shown("5.5", '[=0]"-";General') == ("raw", None) @pytest.mark.parametrize( ("raw", "fmt", "expected"), [ ("5", "[<10]0;[>=10]0,", "5.00"), ("20000", "[<10]0;[>=10]0,", "20.00"), ("5", "[<>5]0;0.0", "5.0"), ], ) def test_comparison_conditions(raw: str, fmt: str, expected: str) -> None: kind, value = shown(raw, fmt) assert kind == "value" assert value is not None assert Decimal(expected) == value def test_fourth_section_ignored() -> None: assert shown("1500", "#,##0,;(#,##0,);0;@") == ("value", Decimal("2.00")) def test_semicolon_in_quotes_does_not_split() -> None: assert shown("0", '[=0]"a;b"') == ("unparsed", None) def test_escaped_char_is_decoration() -> None: assert shown("1500", "#,##0\\ \\р") == ("value", Decimal("1500.00")) def test_huge_value_out_of_range_is_unparsed() -> None: assert shown("1" + "0" * 40, "#,##0") == ("unparsed", None) def test_max_numeric_18_2_range() -> None: """Excel показывает 15 значащих цифр: 16-значное целое округляется до 1E+16, что уже вне numeric(18,2).""" assert shown("999999999999999", "0") == ("value", Decimal("999999999999999.00")) assert shown("9999999999999999", "0") == ("unparsed", None) assert shown("99999999999999999", "0") == ("unparsed", None) def test_scaled_huge_value_fits() -> None: assert shown("99999999999999999000", "0,") == ("unparsed", None) assert shown("999999999999999000", "0,") == ("value", Decimal("999999999999999.00")) def test_condition_compares_with_excel_15_digit_precision() -> None: """Из xls значение приходит как -1486533582.3500001, условие записано `-1486533582.35`: Excel считает их равными.""" fmt = '[=-1486533582.35]"(1\u00a0486\u00a0534)"' result = display_amount(Decimal("-1486533582.3500001"), fmt) assert result == FormatResult("value", Decimal("-1486534")) def test_value_is_rounded_to_15_digits_half_up_before_scaling() -> None: """Артефакт float 1500499.9999999998 в Excel — 1500500, после деления на 1000 половина округляется вверх.""" assert shown("1500499.9999999998", "#,##0,") == ("value", Decimal("1501")) def test_condition_operand_is_rounded_to_15_digits() -> None: """Литерал «-» — «нет значения»; «x» дал бы unparsed по R-5 независимо от условия, поэтому берём прочерк.""" result = display_amount(Decimal("0.1234567890123456"), '[=0.1234567890123456]"-"') assert result == FormatResult("value", None) def test_condition_that_does_not_match_the_pattern_is_unparsed() -> None: assert shown("0", '[=1E-5]"x"') == ("unparsed", None)