"""Regression checks for discounted MoySklad positions in 1C exports.""" import sys import types import unittest from decimal import Decimal from unittest.mock import patch from lxml import etree with patch.dict( sys.modules, {"config": types.SimpleNamespace( settings=types.SimpleNamespace(include_accounting_accounts=False) )}, ): from modules.xml_generator import XML1CGenerator class DiscountExportTests(unittest.TestCase): def setUp(self): self.generator = XML1CGenerator() @staticmethod def order_data(positions, sum_kopecks): return { "order": { "id": "example", "name": "example", "moment": "2026-09-15 09:00:00", "sum": sum_kopecks, }, "organization": {}, "counterparty": {}, "positions": positions, } @staticmethod def values(xml): root = etree.fromstring(xml.encode("utf-8")) document = root.xpath('//*[local-name()="Документ.РеализацияТоваровУслуг"]')[0] header = document.xpath('./*[local-name()="Сумма"]/text()')[0] rows = document.xpath( './*[local-name()="Товары" or local-name()="Услуги"]' '/*[local-name()="Строка"]' ) return header, [ { name: (row.xpath('./*[local-name()=$name]/text()', name=name) or [None])[0] for name in ( "Цена", "Сумма", "СуммаНДС", "ВидСкидки", "ПроцентСкидки", "СуммаСкидки", "СуммаСУчетомСкидки", ) } for row in rows ] def test_discount_applies_to_goods_and_services_in_both_export_modes(self): data = self.order_data([ { "quantity": 2, "price": 10000, "discount": 10, "vat": 20, "assortment": {"name": "Product", "meta": {"type": "product"}}, }, { "quantity": 1, "price": 10000, "discount": 10, "vat": 20, "assortment": {"name": "Service", "meta": {"type": "service"}}, }, ], 27000) for xml in ( self.generator.generate_realization(data), self.generator.generate_batch_realization([data]), ): with self.subTest(export=xml[:50]): header, rows = self.values(xml) self.assertEqual(header, "270") self.assertEqual( [(r["Цена"], r["Сумма"], r["СуммаНДС"]) for r in rows], [("100.00", "200.00", "30.00"), ("90.00", "90.00", "15.00")], ) self.assertEqual(rows[0]["ВидСкидки"], "НаТовар") self.assertEqual(rows[0]["ПроцентСкидки"], "10.00") self.assertEqual(rows[0]["СуммаСкидки"], "20.00") self.assertEqual(rows[0]["СуммаСУчетомСкидки"], "180.00") self.assertEqual( Decimal(rows[0]["СуммаСУчетомСкидки"]) + Decimal(rows[1]["Сумма"]), Decimal(header), ) def test_fractional_discount_preserves_kopecks(self): data = self.order_data([{ "quantity": 2, "price": 10001, "discount": 12.5, "vat": 20, "assortment": {"name": "Product", "meta": {"type": "product"}}, }], 17502) header, rows = self.values(self.generator.generate_realization(data)) self.assertEqual(header, "175.02") self.assertEqual(rows[0]["Цена"], "100.01") self.assertEqual(rows[0]["Сумма"], "200.02") self.assertEqual(rows[0]["ПроцентСкидки"], "12.50") self.assertEqual(rows[0]["СуммаСкидки"], "25.00") self.assertEqual(rows[0]["СуммаСУчетомСкидки"], "175.02") self.assertEqual(rows[0]["СуммаНДС"], "29.17") def test_position_without_discount_keeps_original_price(self): data = self.order_data([{ "quantity": 1, "price": 10001, "vat": 20, "assortment": {"name": "Product", "meta": {"type": "product"}}, }], 10001) header, rows = self.values(self.generator.generate_realization(data)) self.assertEqual(header, "100.01") self.assertEqual(rows[0]["Цена"], "100.01") self.assertEqual(rows[0]["Сумма"], "100.01") self.assertIsNone(rows[0]["ПроцентСкидки"]) def test_service_discount_uses_net_amount_with_two_decimal_price(self): data = self.order_data([{ "quantity": 2, "price": 10001, "discount": 12.5, "vat": 20, "assortment": {"name": "Service", "meta": {"type": "service"}}, }], 17502) header, rows = self.values(self.generator.generate_realization(data)) self.assertEqual(header, "175.02") self.assertEqual(rows[0]["Цена"], "87.51") self.assertEqual(rows[0]["Сумма"], "175.02") self.assertEqual(rows[0]["СуммаНДС"], "29.17") self.assertIsNone(rows[0]["ПроцентСкидки"]) if __name__ == "__main__": unittest.main()