Когда обмен успешен, а значение уже другое
Внешняя система передаёт сумму текстом, обработчик преобразует её в число, CRM принимает запись. В журнале нет исключения. Но проверка преобразования строки в число должна отвечать на более точный вопрос: осталось ли это тем значением, которое отправитель намеревался передать? Положительный ответ библиотеки подтверждает только выполнение её правил. Правила поля, единица измерения и допустимая точность могут быть другими.
Например, строка 1,234 допускает два чтения: одна целая и двести тридцать четыре тысячных либо одна тысяча двести тридцать четыре. Разница возникает не после сложного расчёта, а на входе. Если разработчик ищет только исключения, оба успешных результата выглядят исправно. Если он сравнивает значение с договором обмена, становится видно, что для выбора недостаточно самой последовательности символов.
Мы проверили этот механизм на .NET 8.0.27: одинаковые строки разобрали с двумя культурами, затем применили собственный ограниченный формат и проверили запись в новую временную SQLite. Это самостоятельный синтетический опыт с опубликованными ниже входами и условиями. Мы не испытывали рабочую CRM, не воспроизводили дефект BPMSoft и не измеряли потери заказчика. Результат относится к конкретной границе преобразования, которую можно повторить отдельно от платформы.
Практическая задача статьи — помочь разработчику подготовить такую границу для одного поля. Сначала разберём механизмы и выбор правил. Затем соберём набор проверок и способ доказать, что неподходящий вход не изменяет сохранённое значение. Исправный обмен начинается не с особенно терпимого преобразователя, а с понятного ответа: какие записи вообще разрешено принимать.
Одна строка, два числа
В .NET культура описывает, среди прочего, десятичный разделитель и разделитель групп разрядов. Поставщик формата может быть указан явно; если его не передать, многие перегрузки используют текущую культуру. Поэтому настройки окружения становятся частью поведения обработки, даже когда в исходном коде это решение не видно. Механизм описан в документации Microsoft по анализу числовых строк.
Для проверки мы вызвали decimal.TryParse с NumberStyles.Number. При en-US строка 1,234 дала 1234, при ru-RU — 1.234. Обе попытки завершились успешно. Точка в записи результата здесь служит единым способом показа, чтобы сравнение не зависело от вывода консоли. Это наблюдение нашего запуска, а не предположение о том, как настроена конкретная CRM.
Отсюда следует важная граница выбора. Последовательно попробовать русскую культуру, затем американскую — не значит определить намерение отправителя. Для неоднозначной строки первая попытка уже успешна. Перестановка двух попыток меняет результат без изменения входа. Проверка «получилось ли число» в таком алгоритме не содержит критерия, позволяющего выбрать правильное число.
Для машинного обмена культуру и грамматику разумно связывать с договором конкретного источника. Для человеческой формы допустим другой путь: показывать понятный локальный формат, проверять его и преобразовывать в согласованное представление на отдельной границе. Эти подходы совместимы. Ошибка появляется, когда пользовательский способ ввода незаметно становится эвристикой серверного обмена. Поддержка нескольких форматов возможна, но каждый из них должен иметь явный признак выбора: источник, версию сообщения или отдельное поле формата.

Собственный опыт НТА на .NET 8.0.27: обе попытки успешны. Изображение поясняет влияние культуры, а не сравнивает качество стран или программ.
Что именно обещает TryParse
Метод TryParse удобен тем, что сообщает о неудачном преобразовании логическим результатом. Но вызывающий код обязан использовать этот результат. Для рассматриваемой перегрузки Decimal.TryParse при отказе выходной параметр содержит ноль. Следовательно, прочитать out и сохранить его независимо от возвращённого признака — значит смешать ошибочный вход с настоящим нулём. Это прямо следует из контракта метода.
Есть и более тонкая граница: успешный разбор не всегда означает сохранение всей десятичной точности исходной строки. В нашем запуске 0.12345678901234567890123456789 при en-US превратилось в 0.1234567890123456789012345679. Возвращён успех, но последние цифры изменились. Библиотека работает в пределах представления decimal; она не обещает произвольную точность текста.
Полезно разделять три вопроса. Первый: разрешена ли такая последовательность символов договором? Второй: может ли выбранный числовой тип представить её значение без запрещённой потери? Третий: допустимо ли это значение для конкретного поля? Один вызов общего преобразователя не обязан отвечать на все три. Его успешность становится условием следующей проверки, а не окончательным разрешением записи.
Ноль, пустота и отсутствие поля также требуют отдельных решений. Если пустая строка означает «не прислано», она не становится нулём только потому, что так проще заполнить объект. Если ноль разрешён, его нужно принимать как самостоятельное значение. Если отрицательное количество запрещено, синтаксически правильный минус всё равно не проходит предметную проверку. Эти условия задаёт владелец данных; разработчик делает их исполнимыми и наблюдаемыми.
Договор поля раньше преобразователя
Для воспроизводимого примера зададим ограниченный машинный формат. Разрешены цифры ASCII, необязательный начальный минус и точка с одной или двумя дробными цифрами. Пробелы, плюс, запятая, группировка разрядов и экспонента запрещены. Максимальная длина — 32 символа. Значение находится между -9999999999.99 и 9999999999.99. Дополнительные дробные знаки приводят к отказу; автоматического округления нет.
Это выбранные условия опыта, а не универсальная рекомендация для всех сумм. Для массы может требоваться больше дробных разрядов, для счётчика — только целое, для идентификатора — вообще строковый тип. В частности, ведущие нули идентификатора нельзя терять преобразованием в число. У нас 00012.30 разрешено и равно 12.30, потому что договор сохраняет числовое значение, а не исходное написание. Иное требование потребует отдельной проверки формы.
Почему формат проверяется отдельно от NumberStyles? Набор Number разрешает больше элементов: в том числе начальные и конечные пробелы, знак и разделитель групп. Полный состав опубликован в справочнике NumberStyles. Широкий набор полезен для своей задачи, но не выражает узкий договор автоматически.
Грамматика нашего примера записывается как \A-?[0-9]+(?:\.[0-9]{1,2})?\z. Она охватывает всю строку, требует цифр до точки и после неё, если точка есть. Здесь используются именно [0-9]: расширение до других цифровых символов не происходит случайно. Это небольшой, обозримый язык одного поля. При его изменении обновляются одновременно правило, ожидаемые результаты и версия обмена, а не только выражение в обработчике.
Почему очистка ввода меняет смысл
Убрать все пробелы и заменить запятые точками кажется дружелюбным решением. Но преобразование текста до проверки может уничтожить сведения о том, что именно передал источник. Строка с разделителями групп и строка с дробным разделителем требуют разных действий. Если сначала удалить запятую, 1,234 станет 1234; если заменить её точкой — 1.234. Технически оба действия просты, но их смысл противоположен.
В нашем узком формате такой вход отклоняется. Это не запрет локализованных чисел вообще. Если отдельный поставщик гарантирует русскоязычную запись, можно описать адаптер: какие пробелы он допускает, что считается дробным разделителем, разрешены ли группы и как выглядит канонический выход. Ключевое отличие от угадывания — преобразование опирается на признак источника и проверяемое соглашение, а не на удобство текущей строки.
Пробелы тоже неодинаковы. В набор включены обычный пробел и неразрывный U+00A0. На экране они могут быть похожи, но условие договора должно говорить, принимаются ли они и где. Слепое удаление всех пробельных символов скрывает нарушение формата. Для человеческого ввода нормализация иногда оправданна; тогда её нужно описывать как отдельную разрешённую операцию и проверять результат, а не прятать внутри «безопасного» разбора.
Похожая ситуация с остатком текста. Принять числовое начало строки, проигнорировав tail, — значит принять иной язык, чем «поле целиком содержит число». В нашем опыте 12.34tail отвергнуто. Для свободного текста извлечение числа может быть самостоятельной задачей, но оно не заменяет проверку обязательного числового поля обмена. У этих задач разные критерии успеха и разные права на изменение сообщения.
Точность проверяют до её потери
Если строку уже преобразовали в ограниченный тип, сравнение с тем же типом не восстановит потерянные цифры. После округления длинной дроби обе стороны такого сравнения могут содержать одинаковое изменённое значение. Поэтому условие «разобрали, отформатировали и снова разобрали» само по себе не доказывает числовую точность относительно исходного текста. Оно проверяет устойчивость уже полученного представления.
В нашем примере масштаб ограничен двумя цифрами после точки ещё на текстовой стадии. Затем диапазон ограничен десятью целыми разрядами. Совокупность этих условий задаёт существенно более узкое множество, чем возможности decimal. Для такого множества можно проверять точное представление без поддержки произвольных длинных дробей. Мы не объявляем любой успешный decimal.TryParse точным; мы заранее ограничиваем допустимый вход.
Масштаб и точность — разные свойства. Масштаб обозначает число дробных разрядов, а точность связана с общим числом значащих десятичных цифр. Строка 12.340 численно равна 12.34, но имеет три дробных знака. Наш договор отвергает её по форме. Если система должна принимать завершающие нули сверх масштаба, это допустимое другое правило; его нужно реализовать без молчаливого принятия ненулевого остатка.
Для произвольной точности понадобится иной механизм: например, работа с целым коэффициентом и отдельным масштабом, с явным ограничением размера входа. Это усложняет реализацию и проверку. Выбирать его стоит при подтверждённом требовании поля, а не ради универсальности. В любом варианте пределы должны существовать: длина сообщения и допустимые разряды влияют и на смысл, и на стоимость обработки. Бесконечная терпимость здесь не является преимуществом.
Округление — отдельное решение
Преобразование отвечает на вопрос «какое значение записано в строке». Округление отвечает на другой: «какое новое значение разрешено получить по выбранному правилу». Если объединить их без явного решения, потеря точности станет обычным побочным эффектом сохранения. Для поля, где это запрещено, лишний дробный разряд должен приводить к отказу до записи, даже когда библиотека способна округлить число.
Мы отдельно проверили Decimal.Round с двумя режимами. Для 1.005 до двух знаков ToEven дал 1.00, а AwayFromZero — 1.01. Для 1.015 оба дали 1.02. Для -1.005 результаты составили -1.00 и -1.01. Эти наблюдения показывают, почему проверки только на положительном числе, далёком от середины, недостаточно. Выбор перегрузки и режима описан в документации Decimal.Round.
Статья не устанавливает бухгалтерскую политику. Если округление разрешено, владелец данных определяет момент, масштаб и режим. Разработчик фиксирует исходное значение, разрешённую операцию и ожидаемый результат в проверках. Для некоторых процессов округление на каждой строке и округление итоговой суммы дадут разные результаты; выбирать между ними только по удобству метода нельзя.
Для нашего ограниченного обмена решение проще: не округлять вообще. Строки 1.234 и 12.340 не проходят выбранную грамматику. Это делает отказ предсказуемым, но требует от отправителя соблюдения согласованного формата. Если его процесс действительно нуждается в трёх знаках, отказ нельзя «исправлять» локальным сокращением: сначала меняется договор, затем обе стороны проверяют новый маршрут. Техническая строгость полезна ровно в пределах согласованных требований.
Сохранение тоже способно изменить число
После преобразователя остаётся ещё одна граница — тип поля хранилища. Документация PostgreSQL 16 для numeric(p,s) указывает: избыточный масштаб округляется до масштаба столбца, после чего проверяется допустимое число целых разрядов. Поэтому успешная запись не обязательно доказывает отсутствие изменения. Это свойство числовых типов PostgreSQL, а не результат нашего испытания этой СУБД.
Здесь полезно различить доказательство документацией и доказательство запуском. Мы использовали новую временную SQLite, где значение сохранялось целым числом сотых. У такого опыта другая модель хранения. Он проверяет ветвление перед записью и неизменность записи после отказа, но не подтверждает поведение столбца PostgreSQL или адаптера CRM. Утверждать, что все эти границы уже испытаны, было бы неверно.
Для собственной интеграции после согласования формата нужно проверить фактический тип поля, параметры драйвера и прочитанное обратно значение. Если принимающая система выполняет дополнительное преобразование, его включают в договор и приёмку. Сравнение только объекта перед отправкой с ожидаемым числом оставляет последнюю границу непроверенной. Сравнение прочитанного результата с тем же уже округлённым объектом, в свою очередь, не заменяет проверку исходного допуска.
Считать сумму в целых минимальных единицах можно, если единица и масштаб фиксированы, а диапазон целого типа достаточен. Для количества с меняющимся масштабом это решение подходит не всегда. Нельзя механически назвать любые сотые копейками: валюта и единица должны быть известны. В нашем хранилище «сотые» обозначают только способ представления синтетического поля, без утверждения о денежной модели клиента.
Число JSON и числовая строка
В сообщении JSON 1234.56 и "1234.56" — разные значения: первое имеет числовой синтаксис JSON, второе является строкой. В RFC 8259 для чисел задана точка, допускается экспонента и отдельно оговорены пределы диапазона и точности реализаций. Но содержимое строки автоматически не становится числом этого стандарта. Правила строкового поля задаёт договор приложения.
Из этого следует полезное ограничение: наш запрет экспоненты не объявляет запись 1e3 некорректным числом JSON. Он означает лишь, что текст "1e3" не входит в выбранный язык одного поля. Точно так же запятая внутри JSON-строки допустима как символ строки, хотя она не является десятичной точкой числового значения JSON. Проверка транспорта и проверка предметного поля выполняют разные обязанности.
Переход к числовому типу JSON тоже не отменяет вопрос точности. Отправитель, промежуточный обработчик и получатель могут иметь разные числовые представления. Если на одном участке значение стало приближённым, последующая передача правильной десятичной записи не вернёт утраченное. Поэтому менять строку на число только ради избавления от собственного преобразователя недостаточно; проверяется весь согласованный маршрут.
Выбор строкового представления может быть оправдан потребностью передать точные десятичные цифры, но тогда требуется строгая грамматика и пределы. Выбор числового представления может быть удобнее для типизированного обмена, но требует совместимых возможностей сторон. Ни один вариант не является универсально лучшим. Важно, чтобы команда могла объяснить, где значение впервые интерпретируется и какие изменения разрешены на каждом переходе. Общая структура таких договорённостей разобрана в материале НТА о контракте API до интеграции.
Как построить проверяемую границу
Для выбранного формата порядок проверки принципиален. Сначала проверяются наличие и ограничение длины, затем вся строка по грамматике. После этого выполняется преобразование с явными стилями и культурой, проверяется допустимый диапазон и только затем создаётся команда сохранения. Ошибочный результат не должен иметь такой же путь к записи, как успешное значение. Иначе правильная функция останется окружена неправильным вызывающим кодом.
Основная часть преобразователя может выглядеть так; отдельный вызывающий код обязан остановиться при false:
static bool TryRead(string? text, out decimal value)
{
value = 0m;
if (text is null || text.Length > 32 ||
!Regex.IsMatch(text, @"\A-?[0-9]+(?:\.[0-9]{1,2})?\z",
RegexOptions.CultureInvariant))
return false;
if (!decimal.TryParse(text,
NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint,
CultureInfo.InvariantCulture, out var parsed))
return false;
if (parsed < -9999999999.99m || parsed > 9999999999.99m)
return false;
value = parsed;
return true;
}
Для компиляции нужны System.Globalization и System.Text.RegularExpressions. Независимая от окружения культура выбрана потому, что договор задаёт точку; её свойства описаны в справочнике InvariantCulture. Она не определяет правильный формат за поставщика. Если договор использует запятую, нужен другой явно проверенный адаптер.
В прикладном обработчике полезно различать причины отказа: отсутствие, формат, представление, диапазон, запрещённый масштаб. Это облегчает исправление обмена без угадывания. Пользователю или оператору передают понятную причину и идентификатор сообщения, а не обязательно весь исходный документ. Содержание журнала и доступ к нему задаются отдельно: числовое поле может быть частью конфиденциального сообщения даже тогда, когда само число кажется безобидным.
Как доказать отсутствие записи
Правильный результат функции — необходимое, но недостаточное свидетельство. Можно получить false, а затем всё равно выполнить обновление нулём или старым временным значением. Поэтому в опыте после каждого входа проверялось фактически сохранённое поле. На успешном входе выполнялся параметризованный UPDATE, на отказе команда не вызывалась. Значение читалось до и после обработки.
Всего проверены 23 строки. Шесть приняты, семнадцать отклонены. В принятую группу вошли положительная сумма, ноль, отрицательная сотая, обе границы диапазона и запись с ведущими нулями. В отклонённую — разные разделители, пробелы, плюс, экспонента, буквенный хвост, пустота, превышение диапазона, лишний масштаб, нулевой символ и полноширинные цифры. Это состав набора, а не статистика распространённости ошибок.
Перед обработкой 1,234 в последовательности опыта хранилось -9999999999.99. После отказа осталось то же значение. Проверка неизменности прошла для всех семнадцати отказов. Новая база создавалась во временном каталоге и удалялась после завершения; рабочие данные не использовались. Такой способ позволяет увидеть побочный эффект, которого проверка только возвращённого признака не наблюдает.
Для реальной CRM требуется ещё один уровень: подставить фактический адаптер сохранения в разрешённом испытательном окружении и проверить, что отказ не создаёт запись, не изменяет поле и не запускает связанное действие. Наш опыт показывает метод проверки, но не подтверждает эти свойства конкретной установки. Сетевые повторы, конкурентное обновление и частично исполненная операция требуют собственных сценариев; числовая проверка не заменяет весь договор интеграции.

Проверка собственной временной SQLite в целых сотых. Это свидетельство отсутствия записи в данном опыте, а не снимок рабочей CRM.
Набор входов для одного поля
После объяснения механизма можно использовать компактную таблицу как основу своих ожидаемых результатов. Она относится только к выбранному формату: точка, максимум два дробных знака, ограниченный диапазон и отсутствие округления. При изменении договора ответы пересматривают до исправления обработчика. Нельзя выбирать ожидаемое значение по тому, что уже вернула библиотека: иначе проверка только закрепит её текущее поведение.
| Вход | Ожидаемый исход | Что проверяем |
|---|---|---|
1234.56 |
Принять 1234.56 | Обычная точная запись |
0 |
Принять ноль | Ноль отличается от отсутствия |
-0.01 |
Принять -0.01 | Разрешённый начальный минус |
9999999999.99 и -9999999999.99 |
Принять границы | Диапазон включительно |
10000000000.00 и -10000000000.00 |
Отказать | Выход за обе границы |
1,234 и 12,34 |
Отказать | Запятая не разрешена договором |
1.234 и 12.340 |
Отказать | Лишний дробный разряд |
12.34 и 12.34 |
Отказать | Пробел до и после |
1 234.56 и вариант с U+00A0 |
Отказать | Оба вида группировки |
+12.34 |
Отказать | Плюс не разрешён |
1e3 |
Отказать | Экспонента запрещена в этом поле |
12.34tail |
Отказать | Строка должна потребляться целиком |
| Пустая строка | Отказать | Нет числового значения |
| Длинная дробь из опыта | Отказать до разбора | Не допустить скрытого округления |
00012.30 |
Принять 12.30 | Ведущие нули разрешены для числа |
12.34 с U+0000 в конце |
Отказать | Скрытый хвост |
12.34 |
Отказать | Только согласованные цифры ASCII |
Добавьте к каждому отказу условие «поле до обработки равно полю после обработки». К каждому успеху — точное ожидаемое значение после чтения из хранилища. Отдельно добавьте отсутствие самого поля и null, если формат сообщения допускает их: в нашем исполнимом наборе проверяется именно строковый вход, а не весь JSON-документ.
Хорошая таблица объясняет и то, почему строка отвергнута. Если оператор должен исправить сообщение, ему нужны ожидаемый формат и причина отказа. Если условия источника изменились, ответственному за договор нужна версия и перечень затронутых случаев. Простого «ошибка конвертации» недостаточно для обоих действий, хотя технически это сообщение может быть правдой.
Если сообщение содержит несколько числовых полей
Проверка одного значения не определяет поведение всего сообщения. Представим согласованный обмен, где вместе передаются количество и цена: одно поле допустимо, другое отвергнуто. Если обработчик сначала записал первое, а затем обнаружил ошибку второго, сообщение уже исполнено частично. Преобразователь может работать правильно, но результат обработки не соответствует ожидаемой атомарности. Это отдельное свойство вызывающего кода, которое нельзя доказать таблицей одиночных строк.
Для сообщения, которое по договору принимается только целиком, сначала проверяют все обязательные поля и собирают допустимое представление команды. Сохранение начинается после успешной проверки совокупности. Если хранилище и маршрут позволяют транзакционную запись, проверяют её фактическое поведение при сбое. Если внешние действия не входят в одну транзакцию, нужен отдельный порядок согласования результата. Наш одиночный опыт таких гарантий не устанавливает; он показывает, на каком этапе должна закончиться числовая проверка.
Другой допустимый договор — независимая обработка отдельных строк или полей. Тогда частичный успех должен быть видимым: отправителю сообщают, что принято, что отклонено и что осталось без изменения. Нельзя использовать общий положительный статус, если он воспринимается как подтверждение всего сообщения. Выбор между целостной и частичной обработкой принадлежит владельцу процесса и договору обмена, а не библиотеке разбора чисел.
Поэтому после проверки одного поля добавьте один сценарий вызывающего обработчика: правильное значение рядом с неправильным. Заранее укажите ожидаемое состояние всех затрагиваемых записей и проверьте его после обработки. Это расширение собственного приёмочного набора, а не уже выполненное нами испытание многополевого обмена. Оно помогает не переносить узкое доказательство точного числа на более широкое обещание правильной операции.
Что согласовать до включения обмена
Для собственного поля зафиксируйте его назначение, единицу и допустимость отрицательных значений. Затем опишите строковую грамматику, длину, масштаб, диапазон, правило пустоты и поведение при отказе. Если разрешена нормализация, перечислите конкретные преобразования и их основания. Если разрешено округление, определите его отдельно. Эти решения не следует выводить из региональных настроек сервера или названия столбца.
Далее сопоставьте три представления: вход источника, число в обработчике и поле получателя. На каждом переходе назовите возможное изменение и способ его обнаружить. Для приёмки выберите входы на границах, а не только несколько удобных положительных сумм. Повторите проверки при изменении среды выполнения, драйвера, типа столбца или договора поставщика. Это условная инженерная рекомендация, основанная на наблюдённых механизмах, без обещания исключить все ошибки обмена.
Если формат источника неизвестен, сначала нужно получить его описание или согласованные образцы с ожидаемыми значениями. Увеличение числа попыток разбора не заменяет отсутствующее требование. Если источник не может соблюдать общий формат, отдельный адаптер позволяет сделать преобразование видимым и проверяемым. Если нужен произвольный масштаб, узкий двухразрядный преобразователь следует заменить по новому договору, а не постепенно обрастить неявными исключениями.
НТА может помочь описать границы обмена, реализовать адаптер и подготовить приёмку интеграции CRM. Это продолжение обследования процесса, а не обещание исправить все данные одним методом. Связь требований с внедрением разобрана в материале «Внедрение CRM без автоматизации старых ошибок». До обращения к исполнителю у команды уже может быть самостоятельный результат: договор одного поля, таблица ожидаемых исходов и доказательство, что отказ не затрагивает запись.
Тест понедельником
Выберите одно числовое поле действующего обмена. Покажите разработчику и владельцу данных строки 1,234, 1.234, пустоту и значение на границе диапазона. Попросите назвать точный ожидаемый исход до запуска программы. Если ответы расходятся, сначала согласуйте договор. Не выбирайте победителя по текущему поведению сервера: оно может быть именно тем условием, которое требуется изменить.
Затем выполните согласованный набор в изолированном окружении. Для каждого отказа прочитайте сохранённое поле до и после; для каждого допуска сравните прочитанное значение с заранее записанным ожиданием. Проверьте, что настоящий ноль проходит своим путём, а ошибка не становится нулём. Укажите версию среды и способ хранения, чтобы следующий разработчик понимал границу доказательства.
Результат такой проверки — не сообщение «всё преобразовалось». Это ясное правило: какие значения мы принимаем, какие отклоняем и чем подтверждаем отсутствие изменения после отказа. Будущее выдерживает понедельник, когда число сохраняет согласованный смысл, а неподходящая строка останавливается раньше записи.
Важное уведомление
Материал носит информационный характер и не является индивидуальной консультацией, проектной документацией, инструкцией по внедрению или гарантией результата. Применимость описанных подходов зависит от процессов, систем, данных, требований безопасности и иных условий конкретной организации. Перед внедрением или изменением ИТ‑систем необходимо самостоятельно оценить риски и привлечь профильных специалистов.
«Внешняя система передаёт сумму текстом, обработчик преобразует её в число, CRM принимает запись.»
- 01Успешный TryParse не доказывает соответствие договору поля и сохранение любой десятичной точности.
- 02Формат, масштаб и диапазон проверяются до допуска записи; округление требует отдельного решения.
- 03Отказ подтверждают неизменностью сохранённого поля; опыт на SQLite не заменяет испытание CRM.
