"""Отображаемое значение числа по числовому формату ячейки (#42, R-2…R-6): примеры из реальных выгрузок 1С.""" from decimal import Decimal import pytest from src.parser.statements.number_format import FormatResult, display_amount, general_text 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_is_returned_without_range_check() -> None: """P4-4: диапазон numeric(18,2) проверяет пакет (VALUE_OUT_OF_RANGE), адаптер отдаёт показываемое число.""" assert shown("1" + "0" * 40, "#,##0") == ("value", Decimal("1" + "0" * 40 + ".00")) def test_long_integers_keep_all_digits() -> None: """P4-4: длинные целые показываются без потери цифр, диапазон проверяет пакет.""" assert shown("999999999999999", "0") == ("value", Decimal("999999999999999.00")) assert shown("9999999999999999", "0") == ("value", Decimal("9999999999999999.00")) assert shown("99999999999999999", "0") == ("value", Decimal("99999999999999999.00")) def test_scaled_huge_value_is_not_range_checked() -> None: assert shown("99999999999999999000", "0,") == ("value", Decimal("99999999999999999.00")) 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"), "(1\u00a0486\u00a0534)") 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) @pytest.mark.parametrize( ("raw", "fmt", "value", "text"), [ ("106311116.46", "#,##0,", "106311", "106 311"), ("-646532.4", "#,##0;(#,##0)", "-646532", "(646 532)"), ("-1500", "#,##0", "-1500", "-1 500"), ("-1500", "#,##0;#,##0", "-1500", "1 500"), ("1.5", "0.00", "1.50", "1,50"), ("0", '#,##0;-#,##0;"-"', None, "-"), ("12345678901234567", "0", "12345678901234567", "12345678901234567"), ], ) def test_display_text(raw: str, fmt: str, value: str | None, text: str) -> None: shown = display_amount(Decimal(raw), fmt) expected = None if value is None else Decimal(value) assert (shown.kind, shown.value, shown.text) == ("value", expected, text) def test_general_text() -> None: texts = [general_text(Decimal(x)) for x in ("7016006635.0", "106311116.460", "-0.5")] assert texts == ["7016006635", "106311116,46", "-0,5"]